音视频技术应用(18)- 控制播放进度——av_seek_frame()
一. 概述
用于将视频移动到指定的关键帧位置。
二. 函数说明
/**
* 移动视频到指定的关键帧位置
*
* @param s 输入媒体的上下文
* @param stream_index seek的流索引。就是seek时究竟是以音频流索引还是以视频流索引为基准进行seek
* @param timestamp 起始位置的时间戳,单位是AVStream.time_base
* @param flags seek的具体策略
* @return >= 0 on success
*/
int av_seek_frame(AVFromatContext *s, int stream_index, int64_t timestmap, int flags);
第二个参数
timestamp
的详细说明:这里的
timestamp
代表的是想要移动到的起始位置的时间戳
,注意这里是起始位置的时间戳
,不是起始位置的秒数
! 通俗地讲,它就是起始位置的pts
,因此一个10s的视频,你想移动到5s的位置,直接传5是不对的。在 FFmpeg 中,时间戳(timestamp)
的单位是时间基数(time_base)
,时间戳
值乘以时间基
,可以得到实际的时刻值
(以秒等为单位)。例如,如果一个视频帧的dts
是 40,pts
是 160,其time_base
是1/1000
秒,那么可以计算出此视频帧的解码时刻是40 毫秒(40/1000)
,显示时刻是160 毫秒(160/1000)
。FFmpeg 中时间戳(pts/dts)的类型是int64_t
类型,如果把一个time_base
看作一个时钟脉冲,那么 dts/pts 则可以看作是时钟脉冲的计数。第四个参数
flags
的详细说明:该参数一共有以下四种具体取值:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward #define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes #define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes #define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number
下面分别对其进行详细说明:
一. AVSEEK_FLAG_BACKWARD
实际业务中有这样一个场景:用户经常会通过拖动视频底部进度条的方式来跳转到某一帧的位置,那这个时候如何确定当前帧的具体位置呢?有一种办法就是计算当前拖动位置的百分比,然后根据当前视频的总时长,乘以该百分比,得到当前跳转位置的时间,然后根据该时间进行seek操作。
比方说总时长是1000ms, 那如果用户拖动到中间的位置,那就应该seek到500ms的位置,这个时候把500ms传入到上面的函数中进行seek就可以了。可这样有一个问题,你把500ms传入到上面的视频帧当中,是否真的有pts为500ms的视频帧呢?很难,有可能根本就没有pts为500ms的视频帧,这个时候可能有498,也有可能有501的,那到底是取498的呢?还是取501的呢?这个时候就要有一套策略,
AVSEEK_FLAG_BACKWARD
这个FLAG就相当于标识往后走,也就是找pts为501的视频帧。二. AVSEEK_FLAG_BYTE
这种对应的另外一种场景:假设我想移动视频到中间的位置,但是当前视频文件却没有索引, 不过我却知道这个视频文件的大小(1M), 那这个时候要移动视频到中间位置的话,其实就是应该对应在500KB左右的位置,
AVSEEK_FLAG_BYTE
这个FLAG就相当于是根据字节数来移动它的位置。三. AVSEEK_FLAG_ANY
移动视频到任意帧的位置。也就是说,seek到的位置可能是关键帧,也可能是非关键帧,注意:如果移动到的是非关键帧,这个时候解码可能会失败(因为少了前面的关键帧做参考),出现的后果是可能会造成花屏。
四. AVSEEK_FLAG_FRAME
移动视频到关键帧位置。与上面的FLAG不同,这种情况下会强制移动到关键帧位置,比如你要移动的位置是500ms, 但是在500ms没有关键帧,但是在前面400ms的位置有关键帧,它就会移到400ms的位置,这样的话画面就能够正常显示。
注意这种策略也有一定的问题,假设当前视频的GOP是100帧,这100帧的时长是4s, 那这样的话拖动到这4s的任意位置,画面可能都不会改变(由于设置了移动到关键帧,而一个GOP中只有第一帧才是关键帧),这样会给人一个错觉——移动失败了,这点需要注意。