IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> ExoPlayer源码分析--模块及工程篇 -> 正文阅读

[移动开发]ExoPlayer源码分析--模块及工程篇

1 简介

ExoPlayer是android非常流行的开源播放器框架,它以其出色的兼容性,在很大程度上已取代了android系统的媒体播放器,成为媒体类应用软件的标配。
初次使用ExoPlayer,涉及的类比较多,会觉得使用比较复杂;但实际上,ExoPlayer并不是给初级用户使用的,它提供了更高级的定制能力,可以支持比Android原生库更高级的媒体支持能力;甚至,用户还可以自己定义插件,支持不同的媒体格式。
更神奇的地方在于,Exoplayer这些组件并不是一整个的库,而是提供了不同的maven坐标,供客户端组合使用, 如HLS、dash、rtsp, 甚至还提供了一些依赖三方库的插件,如okhttp、ffmpeg、av1、vp9。
这篇文章并不是Exoplayer的使用文档,而是通过分析ExoPlayer的工程组织和扩展机制,了解其中组件化的思想和插件机制的奥妙。

2 基本知识

先看一段Exoplayer中的使用样例源码,涉及了很多类

mediaItems = createMediaItems(intent);  
if (mediaItems.isEmpty()) {  
  return false;  
}  
  
lastSeenTracks = Tracks.EMPTY;  
ExoPlayer.Builder playerBuilder =  
    new ExoPlayer.Builder(/* context= */ this)  
        .setMediaSourceFactory(createMediaSourceFactory());  
setRenderersFactory(  
    playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false));  
player = playerBuilder.build();  
player.setTrackSelectionParameters(trackSelectionParameters);  
player.addListener(new PlayerEventListener());  
player.addAnalyticsListener(new EventLogger());

涉及到的一些重要的单元或类:

  • MediaItem: 播放的单元,一般是一个文件或url
  • Track: 轨道,如视频轨道,音频轨道,字幕等; 音频可能有多个,可以选择不同的语言; 字幕也有可能有多个,可以选择不同的语言的字幕
  • MediaSource: 代表可以被ExoPlayer播放的媒体,一般关联TimeLine用于定义媒体的结构,还提供了一个MediaPeriod用于表示TimeLine的时间段
  • Render: 传统上Android的Render只是指把图片或文本显示到显示器的过程,而这里的Render与Android的概念有点差异,虽然同样把帧数据呈现给用户,但这里不仅指视频和文字帧,也指音频帧
  • Decode: 解码, 如音频解码,视频解码,文字解码等, 一般表示把数据从压缩还原成可以渲染的原始数据的过程

从样例代码中,我们看到了二个Factory类: MediaSourceFactory及RenderersFactory
从设计模式中我们知道,Factory类是工厂类,用于负责实际创建需要使用的产品,如RendersFactory肯定就是用于创造Render
如下是Exoplayer的Glossary里提供的总体架构图,这张图是我们理解ExoPlayer的核心,Exoplayer在使用时,并没有一个非常简单的Facade类,而是需要用户自己装配其中各个组件;
而这个装配过程,就是ExoPlayer优雅架构及可扩展的核心。
exoplayer开发文档中的架构图

如下Render相关的架构图, 从这张图里可以看到,与我们通常理解的先decode后render的过程不一样, ExoPlayer是通过Render把Decoder包装起来的,二者并不是独立的。
Render相关的架构图

3 工程组织

Exoplayer工程里最重要的三个目录:

  • demos: 项目演示工具,非对外发布的库,只是用于告诉大家如何使用
  • extensions: 扩展库,这里的库都是依赖于其他的一些开源项目才能运行,编译步骤也会复杂一些。 但vp9和av1的支持,就需要依赖于开源的c/c++的专门的编解码开源软件
  • library: ExoPlayer库的核心功能都在这里,但它对外发布时,有一些特殊的能力,如dsh, hls, rtsp并不属于核心库的一部分,早分开来作为可扩展的能力发布的

通过readme-graph生成整个工程的模块依赖关系图如下
在这里插入图片描述

工程组织关系由上至下主要分为以下几个层级:

  1. app层
  2. extension层:各种扩展,如ffmpeg, vp9, av1这几个依赖于decoder; 也依赖core; 而cronet和okhttp的扩展则依赖datasource;扩展层是在library-core以上的,因此应用可以按需集成
  3. library-core: 它定义了播放器所需要的各个组件的协调关系
  4. 播放组件层:datasource, decoder, extractor 这一层主要定义了library-core依赖的重要的接口,如果我们学习过SOLID中的依赖倒置原则,应该就更好理解为什么是接口
  5. library-common层:这一层主要就是定义了用到和各种基本的数据类型或util类,如MediaItem, Metadata, Player等

4 vp9扩展解码的实现

4.1 编译构建extension-vp9

vp9是google推出来的新的视频编解码技术,可以免费使用,开源友好,同等视频质量下,视频压缩率比h264要高很多,现在很多开发板厂商都集成硬解vp9的库,我在家里看油管,一般都优先选择vp9的视频格式。
我们以vp9的扩展使用和定义过程,更进一步理解ExoPlayer的extension扩展能力。
根据extensions/vp9路径下的README.md文件,我们知道vp9的编译依赖于google开源的libvpx库,并需要下载ndk进行编译, 但只支持在linux环境和mac Os系统下编译。
具体的编译步骤,可以参考:ExoPlayer vp9目录中的README.md

4.2 使用vp9扩展

在使用时,可以自己定制或实现RenderFactory的子实例,需要在返回的列表中,有一个LibvpxVideoRenderer。
当然也可以直接使用DefaultRendersFactory,并设置extensionRenderMode的参数:

new DefaultRenderersFactory(context.getApplicationContext()).setExtensionRendererMode(extensionRendererMode);

参数取值范围如下:

  • EXTENSION_RENDERER_MODE_OFF: 关闭扩展
  • EXTENSION_RENDERER_MODE_ON: 打开扩展,优先使用核心库
  • EXTENSION_RENDERER_MODE_PREFER: 打开扩展,优先使用扩展库

4.3 vp9相关的类图

可以看到,extension-vp9模块最重要的就是定义了LibvpxVideoRenderer及VpxDecoder, 而这二个类对应的父类,分别定义于library-core及library-decoder模块。
在这里插入图片描述

4.4 DefaultRenderersFactory源码分析

上面的DefaultRenderersFactory里,有一个不对劲的地方。 我们前面说了core模块是底层的模块,它是看不到LibvpxVideoRenderer类的,它怎么能使用它呢?
打开DefaultRenderersFactory.buildVideoRenderers函数:

// 前面的代码添加了缺省的Render到out数组里, 限于篇幅,就不列出代码

// 可以看到prefer的作用就在于,到底添加新元素时,是添加在原数据的前面,还是添加在原数据的后面
int extensionRendererIndex = out.size();  
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {  
  extensionRendererIndex--;  
}  
  
try {  
  // Full class names used for constructor args so the LINT rule triggers if any of them move.  
  // 答案揭晓,反射大法永远是那么有效。考虑到这个只在播放前创建时播放器时使用,因此这个反射不会影响播放效率
  Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer");  
  Constructor<?> constructor =  
      clazz.getConstructor(  
          long.class,  
          android.os.Handler.class,  
          com.google.android.exoplayer2.video.VideoRendererEventListener.class,  
          int.class);  
  Renderer renderer =  
      (Renderer)  
          constructor.newInstance(  
              allowedVideoJoiningTimeMs,  
              eventHandler,  
              eventListener,  
              MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);  
  out.add(extensionRendererIndex++, renderer);  
  Log.i(TAG, "Loaded LibvpxVideoRenderer.");  
} catch (ClassNotFoundException e) {  
  // Expected if the app was built without the extension.  
} catch (Exception e) {  
  // The extension is present, but instantiation failed.  
  throw new RuntimeException("Error instantiating VP9 extension", e);  
}

// 后面还有添加其他extentsion类的,也不再列出

5 参考文档

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 11:21:31  更:2022-12-25 11:24:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/27 11:36:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码