解密FFmpeg播放track mode控制

上一篇文章我们解决了在FFmpeg下如何处理H264和AAC的扩展数据,根据解出的NALU长度恢复了H264的起始码和AAC的ADTS头,这样一般来说播放是没有问题。本篇文章来谈谈如何实现基于FFmpeg的track mode控制,也就是如何用FFmpeg提供的功能来实现基本的seek、快进、快退。好了,废话少了,下面开始基于FFmpeg的track mode之旅。

FFmpeg提供了一个seek函数,原型如下:

int av_seek_frame(AVFormatContext *s, intstream_index, int64_t timestamp, int flags);

参数说明:

s:操作上下文;

stream_index:基本流索引,表示当前的seek是针对哪个基本流,比如视频或者音频等等。

timestamp:要seek的时间点,以time_base或者AV_TIME_BASE为单位。

Flags:seek标志,可以设置为按字节,在按时间seek时取该点之前还是之后的关键帧,以及不按关键帧seek等,详细请参考FFmpeg的avformat.h说明。基于FFmpeg的所有track mode几乎都是用这个函数来直接或间接实现的。

  • 文件的seek功能实现

要转跳到视频100秒(100 000毫秒)处的第一个I帧(如果没有则向前找第一个):

av_seek_frame(pFormatCtx, vid_index, 100000*vid_time_scale/time_base,AVSEEK_FLAG_BACKWARD);

跳到音频80秒(80 000毫秒)处的帧(采样):

av_seek_frame(pFormatCtx, aud_index, 80000*aud_time_scale/ time_base,AVSEEK_FLAG_BACKWARD);

跳到文件开始的地方开始播放:

av_seek_frame(pFormatCtx, vid_index, 0, AVSEEK_FLAG_BACKWARD);

上面的time_scale、time_base都能通过流信息获取到,请参考前面的文章。有的文件不一定能seek成功,可以考虑在失败的情况下将AVSEEK_FLAG_BACKWARD改为AVSEEK_FLAG_ANY再次seek,不过seek到的视频帧可能不是I帧。

这个函数不管你当前在什么时间点上,都可以seek到任何合理位置。比如要实现在当前的基础上向后或向前跳转10秒,我们可以在av_read_frame函数拿到的包中含有当前时间戳的基础上增加或较少一个10000(换算成播放时间单位)再seek即可。所以这个函数可以用做进度的拖放、前进/后退一定时间、循环播放等功能。

  • 快进快退

对于快进来说,一般解码器能实现2倍甚至再高倍速的播放,这种情况直接按照上一篇文章的基本播放流程就可以了。但对于4倍、8倍、16倍、32倍等高速播放,一般不能像传统播放那样一帧一帧的送数据,不只是解码能力问题,数据读取也可能因为带宽不够跟不上,我们只能提取其中的I帧进行播放,将B帧和P帧丢掉。下面我们就来讨论这个过程的实现流程。

快进时,通过当前数据包获得当前的时间PTS,将该PTS换算成时间再加上一小段时间,作为seek时间点向后找关键帧,此时flags可设置为AVSEEK_FLAG_FRAME。之后用av_read_frame获取到该关键帧。完成该帧解码显示后,再在该帧的PTS时间上增加一小段时间后seek,这样一直重复上述过程,流程如下图:

快退时,通过当前数据包获得当前的时间PTS,将该PTS换算成时间再减去一小段时间,作为seek时间点向前找关键帧,此时flags可设置为AVSEEK_FLAG_BACKWARD。之后用av_read_frame获取到该关键帧。完成该帧解码显示后,再在该帧的PTS时间上减去一小段时间后seek,这样一直重复上述结果,过程如下图:

这样,我们通过以上对av_seek_frame函数的运用,即可完成文件playback的各种track mode实现,其实理解了这个函数后,你还会有很多其他办法来实现,这里只是提供了一种简单且占内存少的办法。在特定的情况下还可以先全部走一遍所有帧,并记录下全部的I帧的时间戳、帧编号、位置信息等需要的信息,然后直接从该表里面获取信息后进行seek和读取这些关键帧进行快速播放。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-04-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏抠抠空间

Flask之基本使用与配置

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于...

742
来自专栏java一日一条

HTTPRequest类

HTTPRequest 封装通过 URLFetchService 进行的单个 HTTP 请求。

756
来自专栏Google Dart

为Flutter应用程序添加交互性 顶

你如何修改你的应用程序,使其对用户输入做出反应? 在本教程中,您将为仅包含非交互式小部件的应用添加交互性。 具体来说,您将通过创建一个管理两个无状态小部件的自定...

892
来自专栏前端小作坊

浏览器的overflow事件

Webkit和Firefox其实是原生支持探测元素overflow状态改变的事件。参看这个DEMO:

591
来自专栏Flutter入门

Flutter入门三部曲(3) - 数据传递/状态管理

Flutter数据传递 分为两种方式。一种是沿着数的方向从上向下传递状态。另一种是 从下往上传递状态值。

1603
来自专栏飞雪无情的博客

Go语言经典库使用分析(四)| Gorilla Handlers 源代码实现分析

上一篇 Go语言经典库使用分析(三)| Gorilla Handlers 详细介绍 中介绍了Handlers常用中间件的使用,这一篇介绍下这些中间件实现的原理...

603
来自专栏施炯的IoT开发专栏

《101 Windows Phone 7 Apps》读书笔记-PASSWORDS & SECRETS

课程内容 Ø 加密和解密 Ø 密码输入框 Ø 值转换 Ø DataTimeOffset Ø 可观察集合 Ø INotifyProperty...

1849
来自专栏自动化测试实战

接口测试框架——第三篇:发送(requests)

3346
来自专栏编程思想之路

Android6.0源码分析之menu键弹出popupwindow菜单流程分析

例如上图,在按下菜单键后会弹出对应的菜单选项,准确来说,是在菜单键弹起后出现的一个popupwindow,那么从菜单键弹起到popupwindow创建所涉及到的...

1856
来自专栏.NET开发那点事

使用Microsoft Fakes进行单元测试(1)

一:什么是单元测试 单元测试是对软件进行准确性验证的步骤。单元测试并不进行整个软件功能的测试,仅仅是对于最小工作单元的测试。一般最小工作单元就是指方法/函数等...

1966

扫描关注云+社区