引言
seek功能的基本实现是比较简单的,不过要做到连续正向&逆向seek播放流畅不卡顿要做的优化点其实是比较多的
本篇文章仅讲述如何使用FFmpeg来实现最基本的seek和精准seek功能
seek api
FFmpeg实现seek功能,可以通过avformat.h中提供的两种接口来实现
av_seek_frame
avformat_seek_file
avformat_seek_file
函数内部调用链路如下
可以看到内部优先执行read_seek2
,不支持则回退到av_seek_frame
两种api的接口参数使用都是类似的,这里我们以avformat_seek_file
为例
AVFormatContext *s
:媒体文件打开的上下文结构指针
int stream_index
:流索引;如果指定流索引,则基于AVStream.time_base,如果为-1,则会默认选择一条stream且基于AV_TIME_BASE
int64_t min_ts
:最小可接受时间戳
int64_t max_ts
:最大可接受时间戳
int64_t ts
:目标时间戳,就是我们要seek到的地方
int flags
:
// seek backward
// seek到请求的timestamp之前的最近的关键帧
#define AVSEEK_FLAG_BACKWARD 1
// seeking based on position in bytes
// 基于字节位置的查找,如果flags中包含该标志位,那么时间戳的单位要转换为字节,也就是基于文件中的坐标
#define AVSEEK_FLAG_BYTE 2
// seek to any frame, even non-keyframes
// 可以seek到任意帧,不一定是关键帧,可能导致花屏、马赛克等问题
#define AVSEEK_FLAG_ANY 4
// seeking based on frame number
// 基于帧数量快进
#define AVSEEK_FLAG_FRAME 8
seek操作要点:
- 确定time_base,计算seek的target timestamp
- 刷新解码器缓冲
avcodec_flush_buffers
- 刷新外部自行控制的缓冲区,比如avpacket queue、avframe queue等
- 重新计算音画同步中的startTime
Demo实现
基本Seek功能
UI采用SeekBar,此处我们仅在监听到onStopTrackingTouch
回调时执行seek操作
override fun onStopTrackingTouch(seekBar: SeekBar?) {
seekBar?.let {
val timestamp = seekBar.progress / 100f * mDuration
mPlayer.seek(timestamp)
}
}
基于视频流的seek
代码语言:javascript复制// pos单位为s,转换为视频流的timestamp
int64_t seekPos = av_rescale_q((int64_t)(pos * AV_TIME_BASE), AV_TIME_BASE_Q, mTimeBase);
// 执行seek
int ret = avformat_seek_file(mFtx, mVideoStreamIndex,
INT64_MIN, seekPos, INT64_MAX, AVSEEK_FLAG_BACKWARD);
// 刷新codec缓冲
avcodec_flush_buffers(mCodecContext);
刷新视频avpacket队列
代码语言:javascript复制mVideoPacketQueue->clear();
精准Seek
上面的方式执行后,我们看到的画面是seek到target timestamp最近的关键帧的画面
如果要实现精准seek,从seek点最近的关键帧位置处挨个解码到目标时间点为止即可
代码语言:javascript复制// precision seek
if (mAvFrame->pts < mSeekPos) {
// discard
}
通过ffprobe dump出演示视频的所有视频帧信息
ffprobe -show_frames -select_streams v oceans.mp4 > v_info.txt
可以看到该视频总共有28个关键帧,头两个关键帧间隔了5s多时间
也就是说当我们需要精准seek到第2s、第4s时,都是先seek到pts=0的关键帧上,然后挨个解码到目标seek时间点上(不进行优化的话,可以想象画面卡顿时间是比较长的)
Demo效果
拖动SeekBar结束的时候即可触发seek
参考
1.【FFmpeg源码分析:av_seek_frame()与avformat_seek_file()】
https://blog.csdn.net/u011686167/article/details/123029573