Android MediaPlayer 调用 seekTo 视频进度错误

Android 中遇到的问题,使用 MediaPlayer 播放视频时,调用 seekTo(int) 方法,但是视频实际跳转的进度和传入的时间点并不一致。

搜索相关资料后,发现是视频「关键帧」方面的原因。

最早期的胶卷电影,是以幻灯片的形式放映的,视觉上造成运动的效果;如今虽然使用了各种视频格式,但其本质依然是静态图片的有序播放,每一张图片我们称之为「一帧」。只是相比之下,数字形式的视频基于传输成本的考虑,对图片进行了压缩。

在绝大多数的电影场景中,每一帧图片的内容,大都是相关的。比如虽然人物在进行各种各样的动作,但是背景变化并不大;或者人物在说话中,其头部大致是保持不动的。那么在视频数据中,没有必要在每一帧中都完整地保存当前画面的所有信息(尤其是没有必要保存静止的信息)。可以在某一帧中存储完整的信息,而后序的帧采用「增量更新」的方式,只存储相比前一帧的改变,这样即可大幅减少视频体积。只要保证在场景切换等时候,重新部署一个完整信息帧。 上述中存储完整画面信息的帧,即称为「关键帧」。

参考 Wikipedia - Key Frame 定义:

In video compression, a key frame, also known as an “intra-frame”, is a frame in which a complete image is stored in the data stream. In video compression, only changes that occur from one frame to the next are stored in the data stream, in order to greatly reduce the amount of information that must be stored. This technique capitalizes on the fact that most video sources (such as a typical movie) have only small changes in the image from one frame to the next.

Whenever a drastic change to the image occurs, such as when switching from one camera shot to another, or at a scene change,[1] a key frame must be created. The entire image for the frame must be output when the visual difference between the two frames is so great that representing the new image incrementally from the previous frame would require more data than recreating the whole image.

所以,对于关键帧 frameA 和 frameB 之间的普通帧,缺少 frameA 是无法正常显示的。MediaPlayer 的 seekTo 方法,实际是跳转至 距时间点最近的关键帧 位置,与传入的时间点并不完全一致。

临时的解决方案:使用 ffmpeg 等工具修改视频,使关键帧的分布尽量紧凑,当然副作用是视频体积会相应增大。