Android FFmpeg系列08--seek和精准seek

2022-11-19 09:56:37 浏览数 (1)

引言

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

代码语言:javascript复制
// 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操作

代码语言:javascript复制
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

0 人点赞