ExoPlayer架构详解与源码分析(8)——Loader
系列文章目录
ExoPlayer架构详解与源码分析(1)——前言
ExoPlayer架构详解与源码分析(2)——Player
ExoPlayer架构详解与源码分析(3)——Timeline
ExoPlayer架构详解与源码分析(4)——整体架构
ExoPlayer架构详解与源码分析(5)——MediaSource
ExoPlayer架构详解与源码分析(6)——MediaPeriod
ExoPlayer架构详解与源码分析(7)——SampleQueue
ExoPlayer架构详解与源码分析(8)——Loader
ExoPlayer架构详解与源码分析(9)——TsExtractor
文章目录
- 系列文章目录
- 前言
- ProgressiveMediaPeriod
- Loader
- ExtractingLoadable
- BundledExtractorsAdapter
- Extractor
- 总结
前言
ProgressiveMediaPeriod的左半部分SampleQueue已经在上篇讲完,相对今天说的这部分还算简单,ProgressiveMediaPeriod右半部分主要为Loader,而Loader中及包含数据的获取也包含数据的解析,本篇主要分析Loader的整体机构和数据解析部分结构。
ProgressiveMediaPeriod
还是先预习下上篇的整体结构,本篇主要分析右半半部分的Loader:

图中Loader数据的加载主要靠DataSource,而解析部分主要为Executor
Loader
Loader本质上就是就是一个线程池,初始化时就创建了一个ExecutorService,启动时实例化出一个LoadTask放入线程池中执行。
private final ExecutorService downloadExecutorService;public Loader(String threadNameSuffix) {this.downloadExecutorService =Util.newSingleThreadExecutor(THREAD_NAME_PREFIX + threadNameSuffix);}public <T extends Loadable> long startLoading(T loadable, Callback<T> callback, int defaultMinRetryCount) {Looper looper = Assertions.checkStateNotNull(Looper.myLooper());//获取当前启动线程的looperfatalError = null;long startTimeMs = SystemClock.elapsedRealtime();new LoadTask<>(looper, loadable, callback, defaultMinRetryCount, startTimeMs).start(0);return startTimeMs;}
LoadTask初始化时会传入当前线程的looper和callback,通过looper将后台线程的信息传递到启动线程的callback中执行,所以startLoading的线程必须要包含一个looper,callback也将在启动现场上调用。通常情况下启动线程就是内部播放线程,具体参照之前将的线程模型。
//@LoadTask.java
@Overridepublic void run() {try {boolean shouldLoad;synchronized (this) {shouldLoad = !canceled;executorThread = Thread.currentThread();}if (shouldLoad) {TraceUtil.beginSection("load:" + loadable.getClass().getSimpleName());try {loadable.load();//执行loadable} finally {TraceUtil.endSection();}}synchronized (this) {executorThread = null;// Clear the interrupted flag if set, to avoid it leaking into a subsequent task.Thread.interrupted();}if (!released) {sendEmptyMessage(MSG_FINISH);//将执行结果通过handler发给启动线程looper}...}@Overridepublic void handleMessage(Message msg) {if (released) {return;}if (msg.what == MSG_START) {execute();return;}if (msg.what == MSG_FATAL_ERROR) {throw (Error) msg.obj;}finish();long nowMs = SystemClock.elapsedRealtime();long durationMs = nowMs - startTimeMs;Loader.Callback<T> callback = Assertions.checkNotNull(this.callback);if (canceled) {callback.onLoadCanceled(loadable, nowMs, durationMs, false);return;}switch (msg.what) {case MSG_FINISH:try {callback.onLoadCompleted(loadable, nowMs, durationMs);//执行完毕,在启动线程上调用callback...}}
可以看到最终是调用了loadable.load方法
这个loadable定义在ProgressiveMediaPeriod的ExtractingLoadable中
ExtractingLoadable
ExtractingLoadable主要包含2个东西DataSource和ProgressiveMediaExtractor,DataSource负责从媒体数据源获取数据,而ProgressiveMediaExtractor则负责将获取的数据解析出Format和Metadata等sample到SampleQueue中,由此完成数据的加载
看下最核心的load方法。
@Overridepublic void load() throws IOException {int result = Extractor.RESULT_CONTINUE;while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {//循环多次读取和sample数据try {long position = positionHolder.position;//获取当前的读取位置dataSpec = buildDataSpec(position);//构建dataSource的dataSpeclong length = dataSource.open(dataSpec);//打开读取的流,返回实际数据的长度if (length != C.LENGTH_UNSET) {length += position;//获取加载后的长度onLengthKnown();}...progressiveMediaExtractor.init(//初始化ProgressiveMediaExtractorextractorDataSource,//传入dataSource,此时的dataSource已经open可以直接通过调用dataSource的read获取数据uri,dataSource.getResponseHeaders(),position,//当前读取位置length,//流加载后的总长度extractorOutput);//传入output,最终会关联输出到SampleQueue中...while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {try {//当未open时阻塞当前执行,主要作用是当上层正在决定是否要继续加载时阻塞住下一次加载,//当上层决定完可以继续加载时loadCondition.open()//这里的上层决定一般由LoadControl做出,后面会讲到loadCondition.block();} catch (InterruptedException e) {//如果不继续加载线程会等待,直到调用Loader的cancel方法会executorThread.interrupt();结束当前线程throw new InterruptedIOException();}result = progressiveMediaExtractor.read(positionHolder);//读取并解析数据到,传入positionHolder用于更新下一次读取位置long currentInputPosition = progressiveMediaExtractor.getCurrentInputPosition();//获取当前已经读取到的位置//如果当前进度达到必要去继续加载检查的阈值时调用上层加载检查判断是否下次加载,这个阈值有个默认值为1024*1024if (currentInputPosition > position + continueLoadingCheckIntervalBytes) {position = currentInputPosition;loadCondition.close();//阻塞下次加载handler.post(onContinueLoadingRequestedRunnable);//通过回调询问上层是否需要继续加载}}} finally {if (result == Extractor.RESULT_SEEK) {//如果解析器返回RESULT_SEEKresult = Extractor.RESULT_CONTINUE;//就会再次open数据源,此时的Uri还是同一个,但是position可能已经在解析器中重新指定} else if (progressiveMediaExtractor.getCurrentInputPosition() != C.INDEX_UNSET) {positionHolder.position = progressiveMediaExtractor.getCurrentInputPosition();}DataSourceUtil.closeQuietly(dataSource);//关闭当前流}}}
这里有2个While循环,第一个While循环说明同一个数据源可能会从不同的位置被打开多次,当内循环中解析数据需要SEEK跳过一段数据时就会,返回RESULT_SEEK,这个时候跳出内循环,再次执行外循环。下面会讲到什么时候需要SEEK数据,另外我们注意到Loader从打开数据加载操作1M(默认值)数据时就会阻塞住内循环,停止向SampleQueue中写入数据,而是向上层讯问是否需要继续加载数据,如果上层决定继续加载更多数据,就会调用ProgressiveMediaPeriod continueLoading解开阻塞的锁继续加载。因为我们知道数据是加载到内存中的,如果无限制的加载肯定是不行的,需要有节制的加载和释放数据。
接下看下ProgressiveMediaExtractor的实现BundledExtractorsAdapter。
BundledExtractorsAdapter
这个类相当于包裹了一个Extractor,最终读取工作其实是转发给Extractor,初始化时确认当前流对应的Extractor
@Overridepublic void init(DataReader dataReader,Uri uri,Map<String, List<String>> responseHeaders,long position,long length,ExtractorOutput output)throws IOException {//包装输入流ExtractorInput extractorInput = new DefaultExtractorInput(dataReader, position, length);this.extractorInput = extractorInput;if (extractor != null) {return;}//extractorsFactory通过网络请求的返回的Content-Type和文件的后缀名获取按优先级创建一个Extractor列表Extractor[] extractors = extractorsFactory.createExtractors(uri, responseHeaders);if (extractors.length == 1) {this.extractor = extractors[0];} else {for (Extractor extractor : extractors) {try {//通过调用extractor方法获取流的头部信息最终确定当前流是否可以用这个extractor来解析,如TS就是通过判断0x47同步位来确定的if (extractor.sniff(extractorInput)) {this.extractor = extractor;break;}} catch (EOFException e) {// Do nothing.} finally {Assertions.checkState(this.extractor != null || extractorInput.getPosition() == position);extractorInput.resetPeekPosition();}}if (extractor == null) {//没有支持解析的extractor报错throw new UnrecognizedInputFormatException("None of the available extractors ("+ Util.getCommaDelimitedSimpleClassNames(extractors)+ ") could read the stream.",Assertions.checkNotNull(uri));}}extractor.init(output);//初始化extractor}@Overridepublic int read(PositionHolder positionHolder) throws IOException {return Assertions.checkNotNull(extractor).read(Assertions.checkNotNull(extractorInput), positionHolder);//将数据的读取解析转发给extractor}
Extractor
主要将媒体数据从容器格式中解析出来,支持很多容器格式,每种都做了实现,有很多下图只列了一部分

FileTypes类里定义了这些格式,inferFileTypeFromUri可以看出他们与文件后缀之间的对应关系
public static @FileTypes.Type int inferFileTypeFromUri(Uri uri) {@Nullable String filename = uri.getLastPathSegment();if (filename == null) {return FileTypes.UNKNOWN;} else if (filename.endsWith(EXTENSION_AC3) || filename.endsWith(EXTENSION_EC3)) {return FileTypes.AC3;} else if (filename.endsWith(EXTENSION_AC4)) {return FileTypes.AC4;} else if (filename.endsWith(EXTENSION_ADTS) || filename.endsWith(EXTENSION_AAC)) {return FileTypes.ADTS;} else if (filename.endsWith(EXTENSION_AMR)) {return FileTypes.AMR;} else if (filename.endsWith(EXTENSION_FLAC)) {return FileTypes.FLAC;} else if (filename.endsWith(EXTENSION_FLV)) {return FileTypes.FLV;} else if (filename.endsWith(EXTENSION_MID)|| filename.endsWith(EXTENSION_MIDI)|| filename.endsWith(EXTENSION_SMF)) {return FileTypes.MIDI;} else if (filename.startsWith(EXTENSION_PREFIX_MK,/* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1))|| filename.endsWith(EXTENSION_WEBM)) {return FileTypes.MATROSKA;} else if (filename.endsWith(EXTENSION_MP3)) {return FileTypes.MP3;} else if (filename.endsWith(EXTENSION_MP4)|| filename.startsWith(EXTENSION_PREFIX_M4,/* toffset= */ filename.length() - (EXTENSION_PREFIX_M4.length() + 1))|| filename.startsWith(EXTENSION_PREFIX_MP4,/* toffset= */ filename.length() - (EXTENSION_PREFIX_MP4.length() + 1))|| filename.startsWith(EXTENSION_PREFIX_CMF,/* toffset= */ filename.length() - (EXTENSION_PREFIX_CMF.length() + 1))) {return FileTypes.MP4;} else if (filename.startsWith(EXTENSION_PREFIX_OG,/* toffset= */ filename.length() - (EXTENSION_PREFIX_OG.length() + 1))|| filename.endsWith(EXTENSION_OPUS)) {return FileTypes.OGG;} else if (filename.endsWith(EXTENSION_PS)|| filename.endsWith(EXTENSION_MPEG)|| filename.endsWith(EXTENSION_MPG)|| filename.endsWith(EXTENSION_M2P)) {return FileTypes.PS;} else if (filename.endsWith(EXTENSION_TS)|| filename.startsWith(EXTENSION_PREFIX_TS,/* toffset= */ filename.length() - (EXTENSION_PREFIX_TS.length() + 1))) {return FileTypes.TS;} else if (filename.endsWith(EXTENSION_WAV) || filename.endsWith(EXTENSION_WAVE)) {return FileTypes.WAV;} else if (filename.endsWith(EXTENSION_VTT) || filename.endsWith(EXTENSION_WEBVTT)) {return FileTypes.WEBVTT;} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {return FileTypes.JPEG;} else if (filename.endsWith(EXTENSION_AVI)) {return FileTypes.AVI;} else {return FileTypes.UNKNOWN;}}
从这些代码中可以看出EXO所支持的媒体容器格式。
下篇我们以TS容器格式为例,讲解下TsExtractor,了解媒体数据如何从容器格式中解析出来,最终要交给Readerer渲染的。
总结
Loader主要作用就是将加载逻辑放入线程池中管理,然后通过ExtractingLoadable控制数据的加载,而数据主要由DataSource来获取,解析部分主要为Executor,Executor通过持有已经open的DataSource,不断从DataSource中read出数据用来解析媒体,整个过程都只有一个线程,即使到了数据加载部分也是在同一个线程中同步加载的,在这段同步操作中还将加载的控制权交给了上层组件。下篇将会把TsExtractor作为一个典型的解析器,来分析解析器的具体作用和执行过程。
版权声明 ©
本文为CSDN作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持
相关文章:
ExoPlayer架构详解与源码分析(8)——Loader
系列文章目录 ExoPlayer架构详解与源码分析(1)——前言 ExoPlayer架构详解与源码分析(2)——Player ExoPlayer架构详解与源码分析(3)——Timeline ExoPlayer架构详解与源码分析(4)—…...
ExoPlayer架构详解与源码分析(9)——TsExtractor
系列文章目录 ExoPlayer架构详解与源码分析(1)——前言 ExoPlayer架构详解与源码分析(2)——Player ExoPlayer架构详解与源码分析(3)——Timeline ExoPlayer架构详解与源码分析(4)—…...
【Python 千题 —— 基础篇】输出列表方差
题目描述 题目描述 输出列表的方差。题中有一个包含数字的列表 [10, 39, 13, 48, 32, 10, 9],使用 for 循环获得这个列表中所有项的方差。 输入描述 无输入。 输出描述 输出列表的方差。 示例 示例 ① 输出: 列表的方差是:228.0代码…...
【Spring总结】基于配置的方式来写Spring
本篇文章是对这两天所学的内容做一个总结,涵盖我这两天写的所有笔记: 【Spring】 Spring中的IoC(控制反转)【Spring】Spring中的DI(依赖注入)Dependence Import【Spring】bean的基础配置【Spring】bean的实…...
Unity在Windows选项下没有Auto Streaming
Unity在Windows选项下没有Auto Streaming Unity Auto Streaming插件按网上说的不太好使最终解决方案 Unity Auto Streaming插件 我用的版本是个人版免费版,版本号是:2021.2.5f1c1,我的里边Windows下看不到Auto Streaming选项,就像下边这张图…...
下厨房网站月度最佳栏目菜谱数据获取及分析
目录 概要 源数据获取 写Python代码爬取数据 Scala介绍与数据处理 1.Sacla介绍...
【Java 进阶篇】深入理解 JQuery 事件绑定:标准方式
在前端开发中,处理用户与页面的交互是至关重要的一部分。JQuery作为一个广泛应用的JavaScript库,为我们提供了简便而强大的事件绑定机制,使得我们能够更加灵活地响应用户的行为。本篇博客将深入解析 JQuery 的标准事件绑定方式,为…...
某app c++层3处魔改md5详解
hello everybody,本期是安卓逆向so层魔改md5教学,干货满满,可以细细品味,重点介绍的是so层魔改md5的处理. 常见的魔改md5有: 1:明文加密前处理 2:改初始化魔数 3:改k表中的值 4:改循环左移的次数 本期遇到的是124.且循环左移的次数是动态的,需要前面的加密结果处理生成 目录…...
安装MongoDB
查看MongoDB版本可以执行如下命令 mongod --version 如果是Ubuntu,则直接安装 sudo apt-get install -y mongodb如果是其他,比如Amazon Linux2。 查看Linux系统发行版类型 grep ^NAME /etc/*release 如果是 Amazon Linux 2,则创建一个r…...
C++加持让python程序插上翅膀——利用pybind11进行c++和python联合编程示例
目录 0、前言1、安装 pybind11库c侧python侧 2、C引入bybind11vs增加相关依赖及设置cpp中添加头文件及导出模块cpp中添加numpy相关数据结构的接收和返回编译生成dll后改成导出模块同名文件的.pyd 3、python调用c4、C引入bybind11 0、前言 在当今的计算机视觉和机器学习领域&am…...
ubuntu20.04安装cv2
查看ubuntu的版本 cat /etc/lsb-release DISTRIB_IDUbuntu DISTRIB_RELEASE20.04 DISTRIB_CODENAMEfocal DISTRIB_DESCRIPTION"Ubuntu 20.04.3 LTS"更改镜像源 cp /etc/apt/sources.list /etc/apt/sources.list.bak cat > /etc/apt/sources.listdeb http://mirr…...
Android 13.0 recovery出厂时清理中字体大小的修改
1.前言 在13.0的系统rom定制化开发中,在recovery模块也是系统中比较重要的模块,比如恢复出厂设置,recovery ota升级, 清理缓存等等,在一些1080p的设备,但是density只是240这样的设备,会在恢复出厂设置的时候,显示的字体有点小, 产品要求需要将正在清理的字体调大点,这…...
spring+pom-注意多重依赖时的兼容问题[java.lang.NoSuchMethodError]
背景: 项目中同时引入了依赖A和依赖B,而这两个依赖都依赖于项目C,但它们指定的C版本不一致,导致运行时出现了错误。 报错如: java.lang.NoSuchMethodError 解决方案: 需要在项目pom文件中引入依赖C并指定需…...
Matalab插值详解和源码
转载:Matalab插值详解和源码 - 知乎 (zhihu.com) 插值法 插值法又称“内插法”,是利用函数f (x)在某区间中已知的若干点的函数值,作出适当的特定函数,在区间的其他点上用这特定函数的值作为函数f (x)的近似值,这种方…...
Flask 接口
目录 前言 代码实现 简单接口实现 执行其它程序接口 携带参数访问接口 前言 有时候会想着开个一个接口来访问试试,这里就给出一个基础接口代码示例 代码实现 导入Flask模块,没安装Flask 模块需要进行 安装:pip install flask 使用镜…...
Vue3 toRef函数和toRefs函数
当我们在setup 中的以读取对象属性单独交出去时,我们会发现这样会丢失响应式: setup() {let person reactive({name: "张三",age: 18,job: {type: "前端",salary:10}})return {name: person.name,age: person.age,type: person.jo…...
【论文阅读】(VAE-GAN)Autoencoding beyond pixels using a learned similarity metric
论文地址;[1512.09300] Autoencoding beyond pixels using a learned similarity metric (arxiv.org) / 一、Introduction 主要讲了深度学习中生成模型存在的问题,即常用的相似度度量方式(使用元素误差度量)对于学习良好的生成模型存在一定…...
verilog之wire vs reg区别
文章目录 一、wire vs reg二、实例一、wire vs reg wire线网: 仅支持组合逻辑建模必须由assign语句赋值不能在always块中驱动用于连接子模块的输出用于定义模块的输入端口reg寄存器: 可支持组合逻辑或时序逻辑建模必须在always块中赋值二、实例 wire [7:0] cnt; assign cnt …...
力扣面试经典150题详细解析
刷题的初心 众所周知,算法题对于面试大厂是必不可缺的一环,而且对于提高逻辑思维能力有着不小的提升。所以,对于程序员来讲,无论刚入行,还是从业多年,保持一个清醒的头脑,具备一个良好的设计思…...
【Java 进阶篇】唤醒好运:JQuery 抽奖案例详解
在现代社交网络和电商平台中,抽奖活动成为吸引用户、提升用户参与度的一种常见手段。通过精心设计的抽奖页面,不仅可以增加用户的互动体验,还能在一定程度上提高品牌的知名度。本篇博客将通过详细解析 JQuery 抽奖案例,带领你走进…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目
应用场景: 1、常规某个机器被钓鱼后门攻击后,我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后,我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...
