Android笔记之seekTo

有时候我们在播放视频的时候需要对视频进行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,经过测试如果同时开启多根线程进行操作会导致截出的图片丢失或者顺序错乱。


  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!为了实现精确定位到指定帧的功能,您可以使用以下步骤: 1. 创建一个 MediaExtractor 对象,并设置要解析的媒体文件路径: ```java MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(filePath); ``` 2. 选择要解析的媒体轨道,例如音频轨道或视频轨道: ```java int trackIndex = selectTrack(extractor, mediaType); // mediaType可以是"audio"或"video" extractor.selectTrack(trackIndex); ``` 3. 使用 `seekTo()` 方法定位到指定时间或帧数。对于精确定位到指定帧,您需要知道帧的时间戳,并将其转换为微秒(以微秒为单位的时间戳): ```java long targetTimeUs = frameTimestamp * 1000; // 将帧的时间戳转换为微秒 extractor.seekTo(targetTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC); ``` 或者,如果您知道要跳转到的帧索引,可以使用如下方法: ```java extractor.seekTo(frameIndex, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); ``` 请注意,`frameIndex` 是帧的索引,从0开始计数。 4. 迭代解析从 seekTo() 定位的帧直到您满足的条件为止。例如,如果您想解析一帧视频,则可以使用以下代码: ```java MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); boolean foundFrame = false; while (!foundFrame) { int inputBufferIndex = decoder.dequeueInputBuffer(timeoutUs); if (inputBufferIndex >= 0) { // 将输入数据填充到解码器输入缓冲区 // ... // 解码缓冲区的输出数据 // ... // 检查输出数据是否是您要寻找的帧 // 如果是,则将 foundFrame 设置为 true } } ``` 注意:以上代码只是示意,实际实现可能会有所不同。您需要根据您的需求和媒体类型进行适当的调整。 希望这些信息能对您有所帮助!如果您有任何进一步的问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值