请选择 进入手机版 | 继续访问电脑版

ChinaFFmpeg

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 15738|回复: 2

[Mac OSX] 音视频seek操作源码浅析

[复制链接]
发表于 2013-10-1 17:10:57 | 显示全部楼层 |阅读模式
作者: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。具体代码实现如下:
  1. int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
  2. {
  3.     int ret;

  4.     if (s->iformat->read_seek2 && !s->iformat->read_seek) {
  5.         int64_t min_ts = INT64_MIN, max_ts = INT64_MAX;
  6.         if ((flags & AVSEEK_FLAG_BACKWARD))
  7.             max_ts = timestamp;
  8.         else
  9.             min_ts = timestamp;
  10.         return avformat_seek_file(s, stream_index, min_ts, timestamp, max_ts,
  11.                                   flags & ~AVSEEK_FLAG_BACKWARD);
  12.     }   
  13.     ret = seek_frame_internal(s, stream_index, timestamp, flags);
  14.     if (ret >= 0)
  15.         ret = avformat_queue_attached_pictures(s);

  16.     return ret;
  17. }
复制代码
当seek的时候,av_seek_frame时需要传递参数flag,这个flag为seek_frame的方式标志,标志不同,seek的实现形式也有所不同,以下为seek_frame对应的flag定义
  1. #define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
  2. #define AVSEEK_FLAG_BYTE     2 ///< seeking based on position in bytes
  3. #define AVSEEK_FLAG_ANY      4 ///< seek to any frame, even non-keyframes
  4. #define AVSEEK_FLAG_FRAME    8 ///< seeking based on frame number
复制代码
通常使用的seek方式为BACKWARD,这种方式查找虽然定位并不是非常精确,但是能够很好的处理掉马赛克的问题,因为BACKWARD的方式会去向回查找keyframe处,定位到keyframe处。对应代码在后面能够看到。而 AVSEEK_FLAG_BYTE,则是文件的offset操作,代码如下:
  1. static int seek_frame_byte(AVFormatContext *s, int stream_index, int64_t pos, int flags){
  2.     int64_t pos_min, pos_max;
  3.     pos_min = s->data_offset;
  4.     pos_max = avio_size(s->pb) - 1;
  5.     if     (pos < pos_min) pos= pos_min;
  6.     else if(pos > pos_max) pos= pos_max;
  7.     avio_seek(s->pb, pos, SEEK_SET);
  8.     s->io_repositioned = 1;
  9.     return 0;
  10. }
复制代码
其他的seek一般需要经过两个步骤:
1. read_seek
2. search_timestamp
通常read_seek在容器中实现后,会通过search_timestamp来进入到ff_index_search_timestamp中;在ff_index_search_timestamp中判断是否是我们想要定位的timestamp,并且根据之前传递的flag来进行定位,代码实现如下:

  1. int ff_index_search_timestamp(const AVIndexEntry *entries, int nb_entries,
  2.                               int64_t wanted_timestamp, int flags)
  3. {
  4.     int a, b, m;
  5.     int64_t timestamp;

  6.     a = - 1;
  7.     b = nb_entries;

  8.     //optimize appending index entries at the end
  9.     if(b && entries[b-1].timestamp < wanted_timestamp)
  10.         a= b-1;

  11.     while (b - a > 1) {
  12.         m = (a + b) >> 1;
  13.         timestamp = entries[m].timestamp;
  14.         if(timestamp >= wanted_timestamp)
  15.             b = m;
  16.         if(timestamp <= wanted_timestamp)
  17.             a = m;
  18.     }
  19.     m= (flags & AVSEEK_FLAG_BACKWARD) ? a : b;

  20.     if(!(flags & AVSEEK_FLAG_ANY)){
  21.         while(m>=0 && m<nb_entries && !(entries[m].flags & AVINDEX_KEYFRAME)){
  22.             m += (flags & AVSEEK_FLAG_BACKWARD) ? -1 : 1;
  23.         }
  24.     }

  25.     if(m == nb_entries)
  26.         return -1;
  27.     return  m;
  28. }
复制代码
作者:Chinaffmpeg 孙悟空
当timestamp大于或者小于想要定位的时间戳时,将会对时间戳进行范围定位处理,之前提到的flag中还包含 AVSEEK_FLAG_ANY,当flag为 AVSEEK_FLAG_ANY时定位相对来说是比较精确的,但是区别是如果seek的timestamp处刚好是keyframe时不会遇到问题,但是如果seek处不是keyframe时,将会看到马赛克的出现,如果不是 AVSEEK_FLAG_ANY,而是使用的BACKWARD时,将会根据时间戳向回找对应的帧为keyframe,如果对应的时间戳处为keyframe,那么就seek到了对应的位置,整个seek过程完成。




回复

使用道具 举报

发表于 2013-10-1 17:28:19 | 显示全部楼层
支持大师兄的原创作品。
回复 支持 反对

使用道具 举报

发表于 2013-10-2 08:30:51 | 显示全部楼层

支持大师兄的原创作品
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|Archiver|ChinaFFmpeg

GMT+8, 2024-4-18 20:22 , Processed in 0.050651 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表