MediaExtractor有一个方法如下   http://developer.android.com/intl/es/reference/android/media/MediaExtractor.html

public void seekTo (long timeUs, int mode)

All selected tracks seek near the requested time according to the specified mode.


timeUs是要seek的时间戳,mode是seek的模式,可以是SEEK_TO_PREVIOUS_SYNC, SEEK_TO_CLOSEST_SYNC, SEEK_TO_NEXT_SYNC,分别是seek指定帧的上一帧,最近帧和下一帧。

此方法可用于视频播放时动态定位播放帧,用于动态改变视频播放进度,比如使用seekBar来跟踪视频播放进度,同时可拖动来动态改变播放进度。



之前一直有一个问题困扰我:seekTo方法无法精确定位到指定帧。即使使用的是某一帧精确的时间戳作为seekTo方法的输入参数也无法实现精确定位。

在google, stackoverlfow上一轮搜索,得出的结论是:在每次seekTo方法调用后,MediaCodec必须从关键帧开始解码。因此seekTo方法只会seek到最近的/上一个/下一个关键帧,也就是I-Frame(key frame = I frame = sync frame)。之所以要从关键帧开始解码,是因为每一帧不一定是单独编码的,只有I frame才是帧内编码,而P, B frame都是要参考别的帧来进行编码,因此单独拿出来是不完整的一帧。 关于I,P,B frame的问题可以参考博客:  http://www.cnblogs.com/qingquan/archive/2011/07/27/2118967.html 


stackoverflow上有人对此的做法是:seekTo的输入参数mode设置为SEEK_TO_PREVIOUS_SYNC,即seek的是指定帧的上一个关键帧。然后判断当前的时间戳是否小于定位关键帧的时间戳,如果是就调用MediaExtractor的advance方法,“快进”到指定帧。

extractor.seekTo(expectedPts, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);while (currentPts < expectedPts) {    extractor.advance();}

   

但是这个方法仍不理想,如果seek的位置和当前位置比较远的话,会有一定延迟。而且视频内容偶尔会出现不完整的帧的闪现。

经过一段时间的研究,终于解决了这个问题,现在播放时可以根据seekBar随时拖动到视频任何一帧,不会有任何延迟,甚至可以实现倒播了

因为之前播放视频的是自己用MediaCodec, MediaMuxer等编码合成的视频文件,在编码参数设置的时候,将关键帧间隔KEY_I_FRAME_INTERVAL设置为了1(因为要求参数为整数)。注意这个参数的单位是秒,而不是帧数!网上看到很多例子包括fadden的bigflake和Grafika上都将这里设置为了20几。搞得我一开始还以为是每隔二十几帧就有一个关键帧。如果设置为20几,那么就是说你用MediaCodec编码录制一段20多秒的视频,只有开头的一个关键帧!剩下的都是P或者B帧。

这显然是不合理的。一般来说是每隔1秒有一个关键帧,这样就可以seek到对应秒的关键帧。或者说1秒内如果有30帧,那么这30帧至少有一个关键帧。因此我将KEY_I_FRAME_INTERVAL设置为了1。

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

   
   


但这也是为什么我在播放用这种参数编码的视频的时候,使用seekTo方法不能准确定位帧了。之前有讲,seekTo是定位到关键帧的,如果不是关键帧,那么它会去找上一个/最近一个/下一个关键帧,这取决于你输入参数mode的设置。

因此如果想使用seekBar准确拖动定位到任何一帧播放,必须保证每一帧都是关键帧。
于是,我将KEY_I_FRAME_INTERVAL设置为了0:

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0);
事实也证明这样可以保证录制的每一帧都是关键帧,因此在使用seekTo方法的时候终于可以准确定位任何一帧了。拖动seekBar的时候视频内容也会立刻改变,无论是往前还是往后,都不会有任何延迟和画面不完整的情况(这可比我测试手机使用的自带的视频播放器强哦,自带的播放器在拖动进度条时是不会动态改变的,只有松手了才会跳到对应帧)。据此,也可以实现视频的倒序播放了。


但是,把视频每一帧都设置为关键帧是否合理呢?是否会占太大空间呢?

带着这个疑问,我使用ffmpeg查看了我测试使用的手机(Lenovo X2)内置相机录制的视频。

只需一行代码:

ffprobe -show_frames video.mp4  > frames.txt
打开frames.txt可以看到每一帧的key_frame=1,表示是关键帧



这说明了手机本来录像就是把每一帧都作为关键帧的。

当然,不能以偏概全,于是我使用iPhone 6s录制的一段普通视频和慢动作视频。使用ffmpeg查看,发现每一帧也都是关键帧(慢动作视频1秒有240帧也都全部作为关键帧也是蛮拼的)。


目前我测试的两部手机都是如此,具体为什么手机录的视频每一帧都是关键帧我也不明白。而视频文件体积大小和是否将每一帧设为关键帧似乎不成线性关系,所以将KEY_I_FRAME_INTERVAL设置为0的方案是可行的。

因此只要保证视频每帧都是关键帧,那么seekTo方法就可以精确定位指定帧了。







更多相关文章

  1. Android中多层Fragment嵌套,调用相册返回Uri无法显示图片的问题解
  2. android:Handler MessageQueue Looper分析
  3. android 更新sdk ip
  4. android ImagView缩放方法之一(Bitmap)
  5. Android通过Uri获取文件的路径的方法
  6. Android(安卓)am/pm命令用法
  7. Android(安卓)ListView addScrapView ArrayIndexOutOfBoundsExce
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. Android中解析XML
  2. 【OSC手机App技术解析】- 集成新浪微博An
  3. Google Android开发精华教程
  4. Python中5个你可以能不知道的知识
  5. Python 的高级用法
  6. Python中容器类型转换的三种方法
  7. 使用 Lightly 在线格式化 HTML
  8. php用户登录,异步提交表单注册
  9. 属性与方法重载和命名空间与类自动加载器
  10. Python公共操作的4个运算符(+、*、in、not