|
作者:Chinaffmpeg 孙悟空
在视频解码过程中,经常会遇到快进N秒,快退N秒,或者拖动进度条的操作,那么这个操作的在ffmpeg内部是怎么实现的呢?接下来分析一下对应的实现,首先是通过调用av_seek_frame,根据视频容器的不同,定位方式可能稍威不同,所以,几乎每个容器都有自己实现的seek,而在seek的时候,需要走一下ffmpeg的框架的容器switch部分,也就是libavformat/utils.c中的接口部分,当对支持文件seek的文件的多媒体文件,例如vobsub这类容器中实现了read_seek2的时候,对时间戳的最大最小值进行一个操作,也就是说读取文件可以seek读取,而通常我们所用的3gp,mp4这类文件,还是要通过通用的内部av_seek_internal来进行seek。具体代码实现如下:- int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
- {
- int ret;
- if (s->iformat->read_seek2 && !s->iformat->read_seek) {
- int64_t min_ts = INT64_MIN, max_ts = INT64_MAX;
- if ((flags & AVSEEK_FLAG_BACKWARD))
- max_ts = timestamp;
- else
- min_ts = timestamp;
- return avformat_seek_file(s, stream_index, min_ts, timestamp, max_ts,
- flags & ~AVSEEK_FLAG_BACKWARD);
- }
- ret = seek_frame_internal(s, stream_index, timestamp, flags);
- if (ret >= 0)
- ret = avformat_queue_attached_pictures(s);
- return ret;
- }
复制代码 当seek的时候,av_seek_frame时需要传递参数flag,这个flag为seek_frame的方式标志,标志不同,seek的实现形式也有所不同,以下为seek_frame对应的flag定义- #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
复制代码 通常使用的seek方式为BACKWARD,这种方式查找虽然定位并不是非常精确,但是能够很好的处理掉马赛克的问题,因为BACKWARD的方式会去向回查找keyframe处,定位到keyframe处。对应代码在后面能够看到。而 AVSEEK_FLAG_BYTE,则是文件的offset操作,代码如下:- static int seek_frame_byte(AVFormatContext *s, int stream_index, int64_t pos, int flags){
- int64_t pos_min, pos_max;
- pos_min = s->data_offset;
- pos_max = avio_size(s->pb) - 1;
- if (pos < pos_min) pos= pos_min;
- else if(pos > pos_max) pos= pos_max;
- avio_seek(s->pb, pos, SEEK_SET);
- s->io_repositioned = 1;
- return 0;
- }
复制代码 其他的seek一般需要经过两个步骤:
1. read_seek
2. search_timestamp
通常read_seek在容器中实现后,会通过search_timestamp来进入到ff_index_search_timestamp中;在ff_index_search_timestamp中判断是否是我们想要定位的timestamp,并且根据之前传递的flag来进行定位,代码实现如下:
- int ff_index_search_timestamp(const AVIndexEntry *entries, int nb_entries,
- int64_t wanted_timestamp, int flags)
- {
- int a, b, m;
- int64_t timestamp;
- a = - 1;
- b = nb_entries;
- //optimize appending index entries at the end
- if(b && entries[b-1].timestamp < wanted_timestamp)
- a= b-1;
- while (b - a > 1) {
- m = (a + b) >> 1;
- timestamp = entries[m].timestamp;
- if(timestamp >= wanted_timestamp)
- b = m;
- if(timestamp <= wanted_timestamp)
- a = m;
- }
- m= (flags & AVSEEK_FLAG_BACKWARD) ? a : b;
- if(!(flags & AVSEEK_FLAG_ANY)){
- while(m>=0 && m<nb_entries && !(entries[m].flags & AVINDEX_KEYFRAME)){
- m += (flags & AVSEEK_FLAG_BACKWARD) ? -1 : 1;
- }
- }
- if(m == nb_entries)
- return -1;
- return m;
- }
复制代码 作者:Chinaffmpeg 孙悟空
当timestamp大于或者小于想要定位的时间戳时,将会对时间戳进行范围定位处理,之前提到的flag中还包含 AVSEEK_FLAG_ANY,当flag为 AVSEEK_FLAG_ANY时定位相对来说是比较精确的,但是区别是如果seek的timestamp处刚好是keyframe时不会遇到问题,但是如果seek处不是keyframe时,将会看到马赛克的出现,如果不是 AVSEEK_FLAG_ANY,而是使用的BACKWARD时,将会根据时间戳向回找对应的帧为keyframe,如果对应的时间戳处为keyframe,那么就seek到了对应的位置,整个seek过程完成。
|
|