不少读者很多是以ijkplayer为播放内核。因为编译简单,接口使用方便。核心部分是移植了ffplay.c中的代码。今天分享这篇文章是金山云团队,调研分析的ijkplayer框架代码。涉及了一些基本流程。对于想了解ijkplayer的同学算是很好的借鉴。

随着互联网技术的飞速发展,移动端播放视频的需求如日中天,由此也催生了一批开源/闭源的播放器,但是无论这个播放器功能是否强大、兼容性是否优秀,它的基本模块通常都是由以下部分组成:事务处理、数据的接收和解复用、音视频解码以及渲染,其基本框架如下图所示:

播放器基本框图

针对各种铺天盖地的播放器项目,我们选取了比较出众的ijkplayer进行源码剖析。它是一个基于FFPlay的轻量级Android/iOS视频播放器,实现了跨平台的功能,API易于集成;编译配置可裁剪,方便控制安装包大小。

本文基于k0.7.6版本的ijkplayer,重点分析其C语言实现的核心代码,涉及到不同平台下的封装接口或处理方式时,均以iOS平台为例,Android平台大同小异,请大家自行查阅研究。

一、总体说明

打开ijkplayer,可看到其主要目录结构如下:

  • tool 初始化项目工程脚本

  • config 编译ffmpeg使用的配置文件

  • extra 存放编译ijkplayer所需的依赖源文件, 如ffmpeg、openssl等

  • ijkmedia 核心代码

    • ijkplayer 播放器数据下载及解码相关

    • ijksdl 音视频数据渲染相关

  • ios iOS平台上的上层接口封装以及平台相关方法

  • android android平台上的上层接口封装以及平台相关方法

在功能的具体实现上,iOS和Android平台的差异主要表现在视频硬件解码以及音视频渲染方面,两者实现的载体区别如下表所示:

二、初始化流程

初始化完成的主要工作就是创建播放器对象,打开ijkplayer/ios/IJKMediaDemo/IJKMediaDemo.xcodeproj工程,可看到IJKMoviePlayerViewController类中viewDidLoad方法中创建了IJKFFMoviePlayerController对象,即iOS平台上的播放器对象。查看ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m文件,其初始化方法具体实现如下:可发现在此创建了IjkMediaPlayer结构体实例_mediaPlayer:在该方法中主要完成了三个动作:创建IJKMediaPlayer对象通过ffp_create方法创建了FFPlayer对象,并设置消息处理函数。

创建图像渲染对象SDL_Vout创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分至此已经完成了ijkplayer播放器初始化的相关流程,简单来说,就是创建播放器对象,完成音视频解码、渲染的准备工作。在下一章节中,会重点介绍播放的核心代码。

三、核心代码剖析

ijkplayer实际上是基于ffplay.c实现的,本章节将以该文件为主线,从数据接收、音视频解码、音视频渲染及同步这三大方面进行讲解,要求读者有基本的ffmpeg知识。

ffplay.c中主要的代码调用流程如下图所示:

当外部调用prepareToPlay启动播放后,ijkplayer内部最终会调用到ffplay.c中的

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)

方法,该方法是启动播放器的入口函数,在此会设置player选项,打开audio output,最重要的是调用stream_open方法。从代码中可以看出,stream_open主要做了以下几件事情:

  • 创建存放video/audio解码前数据的videoq/audioq

  • 创建存放video/audio解码后数据的pictq/sampq

  • 创建读数据线程read_thread

  • 创建视频渲染线程video_refresh_thread

说明:subtitle是与video、audio平行的一个stream,ffplay中也支持对它的处理,即创建存放解码前后数据的两个queue,并且当文件中存在subtitle时,还会启动subtitle的解码线程,由于篇幅有限,本文暂时忽略对它的相关介绍。

3.1 数据读取

数据读取的整个过程都是由ffmpeg内部完成的,接收到网络过来的数据后,ffmpeg根据其封装格式,完成了解复用的动作,我们得到的,是音视频分离开的解码前的数据,步骤如下:

创建上下文结构体,这个结构体是最上层的结构体,表示输入上下文设置中断函数,如果出错或者退出,就可以立刻退出打开文件,主要是探测协议类型,如果是网络文件则创建网络链接等探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息打开视频、音频解码器。在此会打开相应解码器,并创建相应的解码线程。读取媒体数据,得到的是音视频分离的解码前数据将音视频数据分别送入相应的queue中重复6、7步,即可不断获取待播放的数据。

3.2 音视频解码

ijkplayer在视频解码上支持软解和硬解两种方式,可在起播前配置优先使用的解码方式,播放过程中不可切换。iOS平台上硬解使用VideoToolbox,Android平台上使用MediaCodec。ijkplayer中的音频解码只支持软解,暂不支持硬解。

3.2.1 视频解码方式选择

在打开解码器的方法中:首先会打开ffmpeg的解码器,然后通过ffpipeline_open_video_decoder创建IJKFF_Pipenode。

第二章节中有介绍,在创建IJKMediaPlayer对象时,通过ffpipeline_create_from_ios创建了pipeline,则func_open_video_decoder函数指针最后指向的是ffpipeline_ios.c中的func_open_video_decoder,其定义如下:如果配置了ffp->videotoolbox,会优先去尝试打开硬件解码器,

node = ffpipenode_create_video_decoder_from_ios_videotoolbox(ffp);

如果硬件解码器打开失败,则会自动切换至软解

node = ffpipenode_create_video_decoder_from_ffplay(ffp);

ffp->videotoolbox需要在起播前通过如下方法配置:

ijkmp_set_option_int(_mediaPlayer, IJKMP_OPT_CATEGORY_PLAYER,

"videotoolbox", 1);

3.2.2 音视频解码

video的解码线程为video_thread,audio的解码线程为audio_thread

不管视频解码还是音频解码,其基本流程都是从解码前的数据缓冲区中取出一帧数据进行解码,完成后放入相应的解码后的数据缓冲区,如下图所示:

音视频解码示意图

本文以video的软解流程为例进行分析,audio的流程可对照研究。

视频解码线程ffpipenode_run_sync中调用的是IJKFF_Pipenode对象中的func_run_syncfunc_run_sync取决于播放前配置的软硬解,假设为软解,则调用get_video_frame中调用了decoder_decode_frame,其定义如下:该方法中从解码前的video queue中取出一帧数据,送入decoder进行解码,解码后的数据在ffplay_video_thread中送入pictq。

3.3 音视频渲染及同步

3.3.1 音频输出

ijkplayer中Android平台使用OpenSL ES或AudioTrack输出音频,iOS平台使用AudioQueue输出音频。

audio output节点,在ffp_prepare_async_l方法中被创建:ffpipeline_open_audio_output方法实际上调用的是IJKFF_Pipeline对象的函数指针func_open_audio_utput,该函数指针在初始化中的ijkmp_ios_create方法中被赋值,最后指向的是func_open_audio_output回到ffplay.c中,如果发现待播放的文件中含有音频,那么在调用stream_component_open打开解码器时,该方法里面也调用audio_open打开了audio output设备。audio_open中配置了音频输出的相关参数SDL_AudioSpec,并通过设置给了Audio Output, iOS平台上即为AudioQueue。

AudioQueue模块在工作过程中,通过不断的callback来获取pcm数据进行播放。

有关AudioQueue的具体内容此处不再介绍。

3.3.2 视频渲染

iOS平台上采用OpenGL渲染解码后的YUV图像,渲染线程为video_refresh_thread,最后渲染图像的方法为video_image_display2,定义如下:从代码实现上可以看出,该线程的主要工作为:

调用frame_queue_peek_last从pictq中读取当前需要显示视频帧

调用SDL_VoutDisplayYUVOverlay进行绘制display_overlay函数指针在前面初始化流程有介绍过,它在方法中被赋值为vout_display_overlay,该方法就是调用OpengGL绘制图像。

3.4.3 音视频同步

对于播放器来说,音视频同步是一个关键点,同时也是一个难点,同步效果的好坏,直接决定着播放器的质量。通常音视频同步的解决方案就是选择一个参考时钟,播放时读取音视频帧上的时间戳,同时参考当前时钟参考时钟上的时间来安排播放。如下图所示:

音视频同步示意图

如果音视频帧的播放时间大于当前参考时钟上的时间,则不急于播放该帧,直到参考时钟达到该帧的时间戳;如果音视频帧的时间戳小于当前参考时钟上的时间,则需要“尽快”播放该帧或丢弃,以便播放进度追上参考时钟。

参考时钟的选择也有多种方式:

  • 选取视频时间戳作为参考时钟源

  • 选取音频时间戳作为参考时钟源

  • 选取外部时间作为参考时钟源

考虑人对视频、和音频的敏感度,在存在音频的情况下,优先选择音频作为主时钟源。

ijkplayer在默认情况下也是使用音频作为参考时钟源,处理同步的过程主要在视频渲染video_refresh_thread的线程中:从上述实现可以看出,该方法中主要循环做两件事情:

  1. 休眠等待,remaining_time的计算在video_refresh

  2. 调用video_refresh方法,刷新视频帧

可见同步的重点是在video_refresh中,下面着重分析该方法:lastvp是上一帧,vp是当前帧,last_duration则是根据当前帧和上一帧的pts,计算出来上一帧的显示时间,经过compute_target_delay方法,计算出显示当前帧需要等待的时间。compute_target_delay方法中,如果发现当前主时钟源不是video,则计算当前视频时钟与主时钟的差值:

  • 如果当前视频帧落后于主时钟源,则需要减小下一帧画面的等待时间;

  • 如果视频帧超前,并且该帧的显示时间大于显示更新门槛,则显示下一帧的时间为超前的时间差加上上一帧的显示时间

  • 如果视频帧超前,并且上一帧的显示时间小于显示更新门槛,则采取加倍延时的策略。

回到video_refreshframe_timer实际上就是上一帧的播放时间,而frame_timer + delay实际上就是当前这一帧的播放时间,如果系统时间还没有到当前这一帧的播放时间,直接跳转至display,而此时is->force_refresh变量为0,不显示当前帧,进入video_refresh_thread中下一次循环,并睡眠等待。如果当前这一帧的播放时间已经过了,并且其和当前系统时间的差值超过了AV_SYNC_THRESHOLD_MAX,则将当前这一帧的播放时间改为系统时间,并在后续判断是否需要丢帧,其目的是为后面帧的播放时间重新调整frame_timer,如果缓冲区中有更多的数据,并且当前的时间已经大于当前帧的持续显示时间,则丢弃当前帧,尝试显示下一帧。否则进入正常显示当前帧的流程,调用video_display2开始渲染。

ijkplayer框架深入剖析相关推荐

  1. Android开发-基于ijkplayer框架开发网络电视直播播放器的实现

    https://blog.csdn.net/fukaimei/article/details/80553709 前 言 ijkplayer框架是由B站在GitHub开源的一款比较好用的开源网络播放器框 ...

  2. ijkplayer播放器剖析(四)音频解码与音频输出机制分析

    ijkplayer播放器剖析系列文章: ijkplayer播放器剖析(一)从应用层分析至Jni层的流程分析 ijkplayer播放器剖析(二)消息机制分析 ijkplayer播放器剖析(三)音频解码与 ...

  3. AirTest 基本使用及框架浅剖析——五分钟上手制作游戏辅助

    简介 Airtest Project 是为编写自动化脚本,达到提升测试效率的一整套解决方案.它可以轻松的扩展到多平台.多引擎上:如基础的 Android和IOS手机应用.App:Windows上的应用 ...

  4. ijkplayer播放器剖析(一)让ijkplayer播起来

    一.引言: ijkplayer是一款对FFmpeg封装非常好的第三方开源播放器,遗憾的是,ijkplayer2.0似乎不开源,并且1.0版本更新也基本停止了,很多公司都会采用ijkplayer作为其播 ...

  5. MFC程序框架的剖析

    和Win32平台创建Windows程序作对比: MFC有个theApp全局变量来代表程序的本身. 1.WinMain 寻找WinMain入口: 在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是 ...

  6. php开发用框架优缺点,剖析PHP开发中主流PHP框架的优缺点

    如今,每个企业乃至最大的商业巨头都希望拥有一个功能齐全的网站以便有效的提高特定业务的品牌价值.PHP 是一种开源的服务器端脚本语言,已经成为定制网站构建解决方案最合适和最有效的语言.在此,小编挑选出了 ...

  7. android自动化框架简要剖析(一):运行原理+基本框架

    android自动化测试原理: 1.将测试apk和被测试apk,运行在一个进程中:通过instrumentation进行线程间的通信 2.通过android.test.AndroidTestCase及 ...

  8. mfc编程 孙鑫_孙鑫VC++视频教程笔记-(3)MFC程序框架的剖析 附1-SDI程序流程图

    1,寻找WinMain人口: 在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码. 路径:MFC|SRC|APPMODUL.CPP: _tWinMain(HINSTANCE hIns ...

  9. 干货:科大讯飞最新语音识别系统和框架深度剖析

    雷锋网按:本文作者魏思,博士,科大讯飞研究院副院长,主要研究领域为语音信号处理.模式识别.人工智能等,并拥有多项业界领先的科研成果.张仕良,潘嘉,张致江科大讯飞研究院研究员.刘聪,王智国科大讯飞研究院 ...

  10. JavaEE开发之SpringBoot框架深入剖析项目实战(魔乐科技)

    课程目录& `) t. s  # W, g, ]" O         第1篇: SpringBoot入门" _0 ?2 t- e; f, r         小节1: S ...

最新文章

  1. linux中用截取一些信息,Linux如何使用cut命令截取文件信息
  2. 2019年的前端学习计划
  3. python输入直角三角形a、b、输出斜边c_编写一个程序,输入直角三角形两条直角边a和b的长度,利用勾股定理计算斜边c的长度。要求结果保留2位...
  4. RTT学习笔记4-线程同步
  5. 不定长度导航的两端对齐
  6. 复杂的指针获取字符串里的内容放入数组
  7. ES中文分词器-ik分词器安装
  8. LWM2M简介-学习记录
  9. 我的个人博客网站是怎么制作的?
  10. 手机影音第十五天,利用service实现后台播放音乐,在通知栏显示当前音乐信息等...
  11. 记录一次在线网页加密PDF解密过程
  12. mysqldb 安装包 linux,Linux下Python MySQLdb模块安装过程及错误解决
  13. 计算并返回 x 的平方根,其中 x 是非负整数。
  14. java咖啡机如何清洗_这样清洗咖啡机最简单有效
  15. Python随堂检测2
  16. 关于pom.xml一直提示Could not transfer artifact(无法搬运)+无法解析maven依赖(更新)的问题,我在学习springboot的过程中遇到的一些问题(持续更新中)
  17. openpyxl单元格居中
  18. java视频文件分片上传
  19. 浅谈我对动态规划的一点理解---大家准备好小板凳,我要开始吹牛皮了~~~
  20. php图片滑动怎么做,抖音里单张图片平移视频怎么制作?影音制作实现一张图片从左到右滑动视频效果...

热门文章

  1. Springboot2.2.6中的RSocket使用, RSocket 进行反应式数据传输
  2. Linux下的Java虚拟机性能监控常用命令
  3. select2使用帮助
  4. new Vue() 和 export default {}及Vue页面组件和标签组件说明与比较(非常重要)
  5. 第四次作业——04树
  6. linux连接lua遇到的问题
  7. Codeforces Round #260 (Div. 1) A - Boredom DP
  8. ubuntu下安装g++
  9. Jenkins的Windows Slave的配置
  10. fatal error C1010: 在查找预编译头时遇到意外的文件结尾