Android笔记之seekTo

来源:互联网 发布:贪心算法的特点 编辑:程序博客网 时间:2024/05/05 12:06
有时候我们在播放视频的时候需要对视频进行seekTo处理。通过videoPlayer或者mediaPlayer的seekTo方法理论上是可以简单的实现。
public native void seekTo(int msec) throws IllegalStateException;

可以看出我们只需要传入一个视频对应的毫秒数就可以直接对视频进行seek。

但是实际上通过一个seekBar来进行测试时发现效果和想象中完全不一致,感觉有严重的跳帧,简单说就是只会seek到很少的特定帧数上而不是能seek到视频的每一帧,于是开始各种百度。最后发现了原因。

在每次seekTo方法调用后,MediaCodec必须从关键帧开始解码。因此seekTo方法只会seek到最近的/上一个/下一个关键帧,也

就是I-Frame(key frame = I frame = sync frame)。之所以要从关键帧开始解码,是因为每一帧不一定是单独编码的,只有I frame才是

帧内编码,而P, B frame都是要参考别的帧来进行编码,因此单独拿出来是不完整的一帧。

因为一般视频都是隔几秒存在一个关键帧,而一秒有24以上的帧数,这样就会导致seekTo效果相当不好。

那么问题来了,应该怎么解决呢。

办法一:seekTo到某一个帧而不是关键帧,需要自己解码渲染图片,而且不确定是否有效。执行效率也不稳定,成本高。

办法二:从源头上解决,增加视频的关键帧。但是会增加视频容量大小。

这两个方法显而易见在自己提供资源的情况下,办法二还简单粗暴很多。我在这里采用的就是这种方法。至于增加关键帧的方法有很多现成的软件可以使用,这里推荐FFmpeg相关方法如下:

ffmpeg.exe -i "D:\in.mp4" -c:v libx264  -preset superfast -x264opts keyint=25 -acodec copy -f  mp4 "D:\out.mp4"
大致意思是在D盘路径下把in.mp4视频文件每隔25帧设置一个关键帧,音轨保持原视频参数,其余使用FFmpeg提供的default值,最后保存为out.mp4文件到D盘。

关于关键帧,我还遇到了另外一种情况。

如果你想从视频中获得到一张缩略图,系统同样提供了现成的API,只需要传入一个对应的毫秒数就可以了,但是和猜想的一样,这个缩略图同样也只能从关键帧中进行获取。以下是对应的工具类。

private static ExecutorService executor = Executors.newFixedThreadPool(1);    private static MediaMetadataRetriever retriever;    public static void AsyncGetBitmapsFromVideo(final Handler handler, String path, final int position, final ImageView imageView) {        retriever = new MediaMetadataRetriever();        retriever.setDataSource(path);        /**         * OPTION_PREVIOUS_SYNC,在给予的时间戳之前获取同步帧         OPTION_NEXT_SYNC,在给予的时间戳之后获取同步帧         OPTION_CLOSEST_SYNC,在给予的时间戳附近         OPTION_CLOSEST,可能返回一个同步或者不同步的帧,但是是在这个时间戳附近,并且对于系统性能的开销         比较大         */        executor.execute(new Runnable() {            @Override            public void run() {                final Bitmap bitmap = retriever.getFrameAtTime(position * 1000 * 1000, MediaMetadataRetriever.OPTION_NEXT_SYNC);                int px40 = PublicUtils.dip2px( 20);                final Bitmap newBitmap = ThumbnailUtils.extractThumbnail(bitmap, px40, px40);                if (bitmap!=null)                handler.post(new Runnable() {                    @Override                    public void run() {                        imageView.setImageBitmap(newBitmap);                    }                });            }        });    }

这里使用了多线程是为了提高性能,最主要的是为了不阻塞UI,由于需要截取多张图片,因此图片的顺序很重要,所以这里才用了线程池来进行操作,并且设置最大线程数为1,经过测试如果同时开启多根线程进行操作会导致截出的图片丢失或者顺序错乱。


0 0