快进和快退

av_seek_frame

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它包括了领先的音/视频编码库libavcodec等。

libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构

和读取音视频帧等功能;

libavcodec:用于各种类型声音/图像编解码;

libavutil:包含一些公共的工具函数;

libswscale:用于视频场景比例缩放、色彩映射转换;

libpostproc:用于后期效果处理;

ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;

ffsever:一个 HTTP 多媒体即时广播串流服务器;

ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示;

FFmpeg是相当强大的多媒体编解码框架,在深入分析其源代码之前必须要有基本的多媒体基础知识,否则其源代码会非常晦涩难懂。本文将从介绍一些基本的多媒体只是,主要是为研读ffmpeg源代码做准备,比如一些编解码部分,只有真正了解了多媒体处理的基本流程,研读ffmpeg源代码才能事半功倍。

下面分析一下多媒体中最基本最核心的视频解码过程,平常我们从网上下载一部电影或者一首歌曲,那么相应的多媒体播放器为我们做好了一切工作,我们只用欣赏就ok了。目前几乎所有的主流多媒体播放器都是基于开源多媒体框架ffmpeg来做的,可见ffmpeg的强大。下面是对一个媒体文件进行解码的主要流程:

1.    解复用(Demux)

当我们打开一个多媒体文件之后,第一步就是解复用,称之为Demux。为什么需要这一步,这一步究竟是做什么的?我们知道在一个多媒体文件中,既包括音频也包括视频,而且音频和视频都是分开进行压缩的,因为音频和视频的压缩算法不一样,既然压缩算法不一样,那么肯定解码也不一样,所以需要对音频和视频分别进行解码。虽然音频和视频是分开进行压缩的,但是为了传输过程的方便,将压缩过的音频和视频捆绑在一起进行传输。所以我们解码的第一步就是将这些绑在一起的音频和视频流分开来,也就是传说中的解复用,所以一句话,解复用这一步就是将文件中捆绑在一起的音频流和视频流分开来以方便后面分别对它们进行解码,下面是Demux之后的效果。

2.    解码(Decode)

这一步不用多说,一个多媒体文件肯定是经过某种或几种格式的压缩的,也就是通常所说的视频和音频编码,编码是为了减少数据量,否则的话对我们的存储设备是一个挑战,如果是流媒体的话对网络带宽也是一个几乎不可能完成的任务。所以我们必须对媒体信息进行尽可能的压缩。

3.   FFmpeg中解码流程对应的API函数

了解了上面的一个媒体文件从打开到解码的流程,就可以很轻松的阅读ffmpeg代码,ffmpeg的框架也基本是按照这个流程来的,但不是每个流程对应一个API,下面这副图是我分析ffmpeg并根据自己的理解得到的ffmpeg解码流程对应的API,我想这幅图应该对理解ffmpeg和编解码有一些帮助。

Ffmpeg中Demux这一步是通过avformat_open_input()这个api来做的,这个api读出文件的头部信息,并做demux,在此之后我们就可以读取媒体文件中的音频和视频流,然后通过av_read_frame()从音频和视频流中读取出基本数据流packet,然后将packet送到avcodec_decode_video2()和相对应的api进行解码。

av_seek_frame 函数用到了一个格式上下文,一个流,一个时间戳和一组标记来作为它的参数。这个函数将会跳转到你所给 的时间戳的位置。时间戳的单位是你传递给函数的流的时基 time_base。然而,你并不是必需要传给它一个流(流可以用-1 来代替)。如果你这样做了, 时基time_base 将会是 avcodec 中的内部时间戳单位,或者是 1000000fps。这就是为什么我们在设置 seek_pos的时候会把位置乘 以 AV_TIME_BASER 的原因。但是,如果给 av_seek_frame 函数的 stream 参数传递传-1,你有时会在播放某些文件的时候遇到问题(比较少见),所以我们会取文件中的第一个流并且把它传递到 av_seek_frame 函数。不要忘记我们也要把时间戳 timestamp 的单位进行转化。

int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
{
    int ret;
    AVStream *st;

    ff_read_frame_flush(s);

    if(flags & AVSEEK_FLAG_BYTE)
        return av_seek_frame_byte(s, stream_index, timestamp, flags);

    if(stream_index < 0){
        stream_index= av_find_default_stream_index(s);
        if(stream_index < 0)
            return -1;

        st= s->streams[stream_index];
       /* timestamp for default must be expressed in AV_TIME_BASE units */
        timestamp = av_rescale(timestamp, st->time_base.den, AV_TIME_BASE * (int64_t)st->time_base.num);
    }

    /* first, we try the format specific seek */
    if (s->iformat->read_seek)
        ret = s->iformat->read_seek(s, stream_index, timestamp, flags);
    else
        ret = -1;
    if (ret >= 0) {
        return 0;
    }

    if(s->iformat->read_timestamp)
        return av_seek_frame_binary(s, stream_index, timestamp, flags);
    else
        return av_seek_frame_generic(s, stream_index, timestamp, flags);
}

utils.c和avformat.h

static int av_seek_frame_byte(AVFormatContext *s, int stream_index, int64_t pos, int flags){
    int64_t pos_min, pos_max;
#if 0
    AVStream *st;

    if (stream_index < 0)
        return -1;

    st= s->streams[stream_index];
#endif

    pos_min = s->data_offset;
    pos_max = url_fsize(s->pb) - 1;

    if     (pos < pos_min) pos= pos_min;
    else if(pos > pos_max) pos= pos_max;

    url_fseek(s->pb, pos, SEEK_SET);

#if 0
    av_update_cur_dts(s, st, ts);
#endif
    return 0;
}
int av_seek_frame_binary(AVFormatContext *s, int stream_index, int64_t target_ts, int flags){
    AVInputFormat *avif= s->iformat;
    int64_t av_uninit(pos_min), av_uninit(pos_max), pos, pos_limit;
    int64_t ts_min, ts_max, ts;
    int index;
    int64_t ret;
    AVStream *st;

    if (stream_index < 0)
        return -1;

#ifdef DEBUG_SEEK
    av_log(s, AV_LOG_DEBUG, "read_seek: %d %"PRId64"\n", stream_index, target_ts);
#endif

    ts_max=
    ts_min= AV_NOPTS_VALUE;
    pos_limit= -1; //gcc falsely says it may be uninitialized

    st= s->streams[stream_index];
    if(st->index_entries){
        AVIndexEntry *e;

        index= av_index_search_timestamp(st, target_ts, flags | AVSEEK_FLAG_BACKWARD); //FIXME whole func must be checked for non-keyframe entries in index case, especially read_timestamp()
        index= FFMAX(index, 0);
        e= &st->index_entries[index];

        if(e->timestamp <= target_ts || e->pos == e->min_distance){
            pos_min= e->pos;
            ts_min= e->timestamp;
#ifdef DEBUG_SEEK
            av_log(s, AV_LOG_DEBUG, "using cached pos_min=0x%"PRIx64" dts_min=%"PRId64"\n",
                   pos_min,ts_min);
#endif
        }else{
            assert(index==0);
        }

        index= av_index_search_timestamp(st, target_ts, flags & ~AVSEEK_FLAG_BACKWARD);
        assert(index < st->nb_index_entries);
        if(index >= 0){
            e= &st->index_entries[index];
            assert(e->timestamp >= target_ts);
            pos_max= e->pos;
            ts_max= e->timestamp;
            pos_limit= pos_max - e->min_distance;
#ifdef DEBUG_SEEK
            av_log(s, AV_LOG_DEBUG, "using cached pos_max=0x%"PRIx64" pos_limit=0x%"PRIx64" dts_max=%"PRId64"\n",
                   pos_max,pos_limit, ts_max);
#endif
        }
    }

    pos= av_gen_search(s, stream_index, target_ts, pos_min, pos_max, pos_limit, ts_min, ts_max, flags, &ts, avif->read_timestamp);
    if(pos<0)
        return -1;

    /* do the seek */
    if ((ret = url_fseek(s->pb, pos, SEEK_SET)) < 0)
        return ret;

    av_update_cur_dts(s, st, ts);

    return 0;
}
static int av_seek_frame_generic(AVFormatContext *s,
                                 int stream_index, int64_t timestamp, int flags)
{
    int index;
    int64_t ret;
    AVStream *st;
    AVIndexEntry *ie;

    st = s->streams[stream_index];

    index = av_index_search_timestamp(st, timestamp, flags);

    if(index < 0 && st->nb_index_entries && timestamp < st->index_entries[0].timestamp)
        return -1;

    if(index < 0 || index==st->nb_index_entries-1){
        int i;
        AVPacket pkt;

        if(st->nb_index_entries){
            assert(st->index_entries);
            ie= &st->index_entries[st->nb_index_entries-1];
            if ((ret = url_fseek(s->pb, ie->pos, SEEK_SET)) < 0)
                return ret;
            av_update_cur_dts(s, st, ie->timestamp);
        }else{
            if ((ret = url_fseek(s->pb, s->data_offset, SEEK_SET)) < 0)
                return ret;
        }
        for(i=0;; i++) {
            int ret;
            do{
                ret = av_read_frame(s, &pkt);
            }while(ret == AVERROR(EAGAIN));
            if(ret<0)
                break;
            av_free_packet(&pkt);
            if(stream_index == pkt.stream_index){
                if((pkt.flags & AV_PKT_FLAG_KEY) && pkt.dts > timestamp)
                    break;
            }
        }
        index = av_index_search_timestamp(st, timestamp, flags);
    }
    if (index < 0)
        return -1;

    ff_read_frame_flush(s);
    if (s->iformat->read_seek){
        if(s->iformat->read_seek(s, stream_index, timestamp, flags) >= 0)
            return 0;
    }
    ie = &st->index_entries[index];
    if ((ret = url_fseek(s->pb, ie->pos, SEEK_SET)) < 0)
        return ret;
    av_update_cur_dts(s, st, ie->timestamp);

    return 0;
}
int av_index_search_timestamp(AVStream *st, int64_t wanted_timestamp,
                              int flags)
{
    AVIndexEntry *entries= st->index_entries;
    int nb_entries= st->nb_index_entries;
    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;
}

aviobuf.c和avio.h

int64_t url_fseek(ByteIOContext *s, int64_t offset, int whence)
{
    int64_t offset1;
    int64_t pos;
    int force = whence & AVSEEK_FORCE;
    whence &= ~AVSEEK_FORCE;

    if(!s)
        return AVERROR(EINVAL);

    pos = s->pos - (s->write_flag ? 0 : (s->buf_end - s->buffer));

    if (whence != SEEK_CUR && whence != SEEK_SET)
        return AVERROR(EINVAL);

    if (whence == SEEK_CUR) {
        offset1 = pos + (s->buf_ptr - s->buffer);
        if (offset == 0)
            return offset1;
        offset += offset1;
    }
    offset1 = offset - pos;
    if (!s->must_flush &&
        offset1 >= 0 && offset1 <= (s->buf_end - s->buffer)) {
        /* can do the seek inside the buffer */
        s->buf_ptr = s->buffer + offset1;
    } else if ((s->is_streamed ||
               offset1 <= s->buf_end + SHORT_SEEK_THRESHOLD - s->buffer) &&
               !s->write_flag && offset1 >= 0 &&
              (whence != SEEK_END || force)) {
        while(s->pos < offset && !s->eof_reached)
            fill_buffer(s);
        if (s->eof_reached)
            return AVERROR_EOF;
        s->buf_ptr = s->buf_end + offset - s->pos;
    } else {
        int64_t res;

#if CONFIG_MUXERS || CONFIG_NETWORK
        if (s->write_flag) {
            flush_buffer(s);
            s->must_flush = 1;
        }
#endif /* CONFIG_MUXERS || CONFIG_NETWORK */
        if (!s->seek)
            return AVERROR(EPIPE);
        if ((res = s->seek(s->opaque, offset, SEEK_SET)) < 0)
            return res;
        if (!s->write_flag)
            s->buf_end = s->buffer;
        s->buf_ptr = s->buffer;
        s->pos = offset;
    }
    s->eof_reached = 0;
    return offset;
}

通过opencv里的VideoCapture的函数set(CV_CAP_PROP_POS_FRAMES,nextFrameNumber),定位到具体的帧号,但最终读取的并不是对应帧的图像.

问题出现的原因:

Opencv底层是通过ffmpeg读取视频.其中定位主要用av_seek_frame()来定位frame的位置.

int av_seek_frame(AVFormatContext *s,int stream_index,int64_t timestamp,int flags)其中最后一个参数有

AVSEEK_FLAG_BACKWARD = 1 // seek backward

AVSEEK_FLAG_BYTE = 2 // seeking based on position in bytes

AVSEEK_FLAG_ANY = 4 // seek to any frame,even non key-frames.

ffmpeg默认的是选取关键帧,opencv里面这个函数的参数flag是0.

因而,进行定位时,若下一帧不是关键帧,进行读取时会出跳跃现象.

将参数改为AVSEEK_FLAG_ANY,虽然可以解决跳跃现象,读取任何帧图像.

但是将会出现花屏现象,因为帧图像解码是需要利用关键帧的图像进行帧间的解码,

若读取帧图像时,其对应关键帧没有被读取解码,将只会对该帧进行帧内解码得到花屏图像.

如何才能解决跳跃现象,但不产生花屏图像?

解决思路:读取下一帧号最相近且前面的关键帧,然后一帧帧的读取视频,直到读到下一帧的帧号为止.

将Opencv2.3.1里面的cap_ffmpeg_impl.cpp里面bool CvCapture_FFMPEG::setProperty( int property_id, double value )函数改成如下实现方式,即可达到准确定位的效果.

http://blog.csdn.net/dtplayer/article/details/24238613

时间: 2024-10-10 23:13:34

快进和快退的相关文章

Video/Audio禁止快进(退)

首先接着上个随笔.上个随笔主要介绍了视频音频的相关操作.属性和方法.这里主要记录一个应用:禁止快进(快退同理). 思路:监听快进事件(此处是监听播放时间更新),利用一个缓存的时间和播放到的时间进行对比,如果时间大于1秒(保险起见使用2秒,以避免在播放的时刻正好在计时的那一刻的尴尬),则表明是快播,给其重置回播放时间即可. 代码: 1 <video 2 id="kingdom-video" 3 :src="xxx" 4 preload 5 controls 6

ffmpeg+sdl教程----编写一个简单的播放器7(处理快进快退命令)

来源:http://blog.csdn.net/mu399/article/details/5818970 这篇教程例子中的程序,让右方向按键为快进10秒,上方向按键为快进60秒,左方向按键为快退10秒,上方向按键为快退60秒,程序中的 av_seek_frame函数可能是用错了,或者函数本身的问题导致按上和右都没反应;按左和下让画面暂停,声音在很短区间内不停播放,这时再按右和下 才正常. [cpp] view plaincopy #include "libavformat/avformat.h

android开发之GestureDetector手势识别(调节音量、亮度、快进和后退)

写UI布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" andr

通信接口是webservice快还是scoket快解决方案

通信接口是webservice快还是scoket快webservice和scoket都可以做为通信接口,一个走HTTP访问,一个走TCP协议访问 问1:通讯速度是webservice快还是scoket快 问2:并发数量是webservice大还是scoket大 问3:大数据包是webservice合适还是scoket更合适 以上一个问题15分,先谢谢各位回复,回复得好,还会给贴加分! ------解决方案--------------------apache的mina2.0对scoket做了封装和

快压下载|快压软件官方下载

压缩软件市场可谓是风起云涌,不仅知名厂商百度.360先后爆出推出压缩软件.2011年4月12日,一款名为"快压"的压缩软件又从天而降!据悉,快压是拥有自主压缩格式以及专利技术的免费压缩软件,它提供了 KZ.7Z 和 ZIP 文件的完整支持,能解压 RAR.MOU.ACE.ARJ.BZ2.CAB.GZ.ISO.JAR.LZH.TAR.UUE.Z 等多种格式文件!快压下载链接软件介绍快压是一款非常实用且免费的压缩和解压缩软件,虽然网上很多用户都在使用winrar,那只是因为winrar的压

topK问题最小堆和快排哪个快

最近一直纠结这个问题.看了很多帖子,决定自己写个例子,实测结果如下: 总数1万个取最大100,快排略快,最小堆偶尔快. 总数10万个取最大100,最小堆略快,快排偶尔快. 总数100万个取最大100,最小堆完胜,快排没戏,而且最小堆大概快了2倍. 总数1000万个取最大100,最小堆完虐,快排没戏,而且最小堆快了大概2倍. 结论:最小堆比快排优秀. 原因: 1.速度确实快. 2.最小堆不需要打乱原数据顺序,而快排会打乱.(并不是快的原因,而是最小堆的优点) 3.如果内存有限,无法加载所有数据,则

SEO实战:用户数据整合帮你快进排名

网站SEO技巧讲过很多,同时很多朋友也有留言说明,大篇的理论,没任何实际效用,对网站排名没有任何帮助.于此,笔者小丹也感到即便理论知识在详尽,没有经过实战的技巧只是废谈.如此,本篇文章小丹将以实例开述总结用户数据带给网站优化的优势. 所谓用户数据第一点我们要知道的就是,网站的服务群体是谁. http://www.jiaoyou8.com/friends_diary/maoyisheng/0_0_0/view_0011481557_no_0_0.2014-12-11.html http://www

快还要更快,让PHP 7 运行更加神速

导读 PHP 7 比5.x 快上很多,即使只有单纯的版本升级就已经很有感,不过大家还是希望它变得越来越快,这时再做些小调整就会更有fu,Let's try it! 事前准备 说到PHP 7,那一定跑不了LAMP 或是LEMP,请先准备好底层服务的安装. [CentOS 7] 整合Apache.MySQL.PHP 7 组成LAMP Server [CentOS 7] 整合Nginx.MariaDB.PHP 7 组成LEMP Server 以前我们要让PHP加快处理速度,通常会配合APC.eAcce

iOS DLNA Cyberlink,PlatinumKit库完成DLNA功能

经过一个多月的研究,终于将iOS DLNA搞定.记录一下. 关于DLNA开发,目前有两个框架.一个Cyberlink,一个platinumkit.Cyberlink的好处就是提供了一套OC的api供你调用,很简单方便.但是此框架有很多问题,且功能不全.platinumkit框架底层为c++,若要用此套框架,就得进行oc和c++的混编,之前我没做过oc和c++的混编,所以去看platinumkit的源码时,觉得头疼无比,浪费了很多时间,但是使用cyberlink框架又有很多功能无法解决,并且框架经