您现在的位置是:首页 >其他 >Android SeekBar控制视频播放进度(二)——seekTo()不准确网站首页其他

Android SeekBar控制视频播放进度(二)——seekTo()不准确

tracydragonlxy 2023-06-16 12:00:02
简介Android SeekBar控制视频播放进度(二)——seekTo()不准确

Android SeekBar控制视频播放进度二——seekTo不准确

简介

上一篇文章中,我们介绍了使用SeekBar控制视频播放,使用过程中发现,对于一些视频,我们拖动SeekBar进度条调节播放进度时,调节到指定位置后,进度条会往回跳,并不会在我们拖动位置继续播放。

网上搜索了解到,VideoView.seekTo()方法的策略决定的。具体看一下seekTo()方法:

seekTo()

  1. 如下是VideoView.seekTo(int msec)的代码实现,我们就是通过调用该方法实现进度调节。通过查看代码,我们知道该方法实际调用的是 MediaPlayer.seekTo(msec);
@Override
public void seekTo(int msec) {
    if (isInPlaybackState()) {
        mMediaPlayer.seekTo(msec);
        mSeekWhenPrepared = 0;
    } else {
        mSeekWhenPrepared = msec;
    }
}
  1. 继续查看 MediaPlayer.seekTo(msec);方法的实现,该方法调用seekTo(long msec, @SeekMode int mode)方法,默认的modeSEEK_PREVIOUS_SYNC
/**
 * Seeks to specified time position.
 * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}.
 *
 * @param msec the offset in milliseconds from the start to seek to
 * @throws IllegalStateException if the internal player engine has not been
 * initialized
 */
public void seekTo(int msec) throws IllegalStateException {
    seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */);
}

/**
 * Moves the media to specified time position by considering the given mode.
 * <p>
 * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user.
 * There is at most one active seekTo processed at any time. If there is a to-be-completed
 * seekTo, new seekTo requests will be queued in such a way that only the last request
 * is kept. When current seekTo is completed, the queued request will be processed if
 * that request is different from just-finished seekTo operation, i.e., the requested
 * position or mode is different.
 *
 * @param msec the offset in milliseconds from the start to seek to.
 * When seeking to the given time position, there is no guarantee that the data source
 * has a frame located at the position. When this happens, a frame nearby will be rendered.
 * If msec is negative, time position zero will be used.
 * If msec is larger than duration, duration will be used.
 * @param mode the mode indicating where exactly to seek to.
 * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame
 * that has a timestamp earlier than or the same as msec. Use
 * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame
 * that has a timestamp later than or the same as msec. Use
 * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame
 * that has a timestamp closest to or the same as msec. Use
 * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may
 * or may not be a sync frame but is closest to or the same as msec.
 * {@link #SEEK_CLOSEST} often has larger performance overhead compared
 * to the other options if there is no sync frame located at msec.
 * @throws IllegalStateException if the internal player engine has not been
 * initialized
 * @throws IllegalArgumentException if the mode is invalid.
 */
public void seekTo(long msec, @SeekMode int mode) {
    if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
        final String msg = "Illegal seek mode: " + mode;
        throw new IllegalArgumentException(msg);
    }
    // TODO: pass long to native, instead of truncating here.
    if (msec > Integer.MAX_VALUE) {
        Log.w(TAG, "seekTo offset " + msec + " is too large, cap to " + Integer.MAX_VALUE);
        msec = Integer.MAX_VALUE;
    } else if (msec < Integer.MIN_VALUE) {
        Log.w(TAG, "seekTo offset " + msec + " is too small, cap to " + Integer.MIN_VALUE);
        msec = Integer.MIN_VALUE;
    }
    _seekTo(msec, mode);
}
  1. SeekMode 有如下几种模式,

SEEK_PREVIOUS_SYNC: seek到上一个关键帧
SEEK_NEXT_SYNC: seek到下一个关键帧
SEEK_CLOSEST_SYNC: seek到最近的关键帧
SEEK_CLOSEST: seek到最近的帧(不需要是关键帧)

    /**
     * Seek modes used in method seekTo(long, int) to move media position
     * to a specified location.
     *
     * Do not change these mode values without updating their counterparts
     * in include/media/IMediaSource.h!
     */
    /**
     * This mode is used with {@link #seekTo(long, int)} to move media position to
     * a sync (or key) frame associated with a data source that is located
     * right before or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_PREVIOUS_SYNC    = 0x00;
    /**
     * This mode is used with {@link #seekTo(long, int)} to move media position to
     * a sync (or key) frame associated with a data source that is located
     * right after or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_NEXT_SYNC        = 0x01;
    /**
     * This mode is used with {@link #seekTo(long, int)} to move media position to
     * a sync (or key) frame associated with a data source that is located
     * closest to (in time) or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_CLOSEST_SYNC     = 0x02;
    /**
     * This mode is used with {@link #seekTo(long, int)} to move media position to
     * a frame (not necessarily a key frame) associated with a data source that
     * is located closest to or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_CLOSEST          = 0x03;

  1. 所以当视频在跳转到相应的 position 位置缺少关键帧的情况下,调用 seekTo 方法是无法在当前位置开始播放。这时会寻找离指定 position 最近的关键帧位置开始播放。
    我们通过seekTo函数调用的实际是默认的mode——SEEK_PREVIOUS_SYNC ,这时会寻找position的上一个关键帧。所以调节视频进度后,视频会往回跳一段,并没有在我们拖动位置继续播放。

视频帧 和 视频关键帧

上面的方法提到了帧和关键帧,下面我们简单的介绍一下两者的关联和区别。我们知道视频是由一帧一帧的图像组成的,而关键帧则是其中的某些帧。理想情况下,我们将所有的普通帧都变为关键帧,那么调节视频播放进度时将不会发生回跳情况。
在这里插入图片描述

解决办法

方法一

根据SeekMode 几种模式的描述,调用时指定modeSEEK_CLOSEST

方法二

对视频源文件进行处理,增加其关键帧数量。使用FFmpeg对视频处理,增加视频的关键帧。

  1. 首先我们通过如下命令查看当前视频中关键帧的数量:
ffprobe -show_frames /Users/Admin/Desktop/test.mp4 >video_log.txt

将视频信息输出到文本文件中,打开video_log.txt文件,搜索关键字pict_type=I查看关键帧。可以看到我们当前视频只有11个关键帧。
在这里插入图片描述
2. 对视频增加关键帧,keyint=30每隔 30 帧设置一个关键帧。命令如下:

ffmpeg.exe -i "/Users/Admin/Desktop/test.mp4" -c:v libx264 -preset superfast -x264opts keyint=30 -acodec copy -f mp4 "/Users/Admin/Desktop/test_out.mp4"

使用步骤1的命令,查看处理后的视频信息。可以看到处理后,我们视频的关键帧数量有99个。
在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。