坑!页面短视频加载又卡又慢?阿里 P8 大佬教你两套办法秒开短视频
这里不详细介绍 MP4 的格式信息。但是可以看出 moov box 对播放很关键,它提供的信息如:宽高、时长、码率、编码格式、帧列表、关键帧列表等等。播放器没有获取到 moov box 是没办法进行播放的。所以下载的数据应该要包含 moov box 再加上几十帧的数据。
做了一个简单的计算:闲鱼短视频一般最长是 30s,feeds 里面的分辨率是 320p,码率是 1141kb/s,ftyp+moov 这个视频的数据量在 31kb 左右(打开文件可以看出 mdat 是从 31754byte 的位置开始的),所以,头部信息+10 帧的数据大约是:(31kb + 1141kb/3)/8 = 51KB
[](
)Proxy
第二个问题:缓存数据播放完成后该怎么切换到网络数据呢?在本地数据播放完成之后,设置一个网络地址给播放器,告诉播放器下载的 offset 是多少,然后继续从网络下载数据播放。这样看起来可行,但是需要播放器提供支持:本地数据播放完成的回调;设置网络 url 并支持 offset。另外,服务端需要支持 range 参数,而且切换到网络播放的时候需要新建立网络连接,很可能会造成卡顿。
最终,我们选择了 proxy 的方式,把 proxy 作为中间人,负责预加载数据、给播放器提供数据,切换逻辑在 proxy 里面来完成。未加入 proxy 之前流程是这样的:
加入了 proxy 之后流程是这样的:
这样做的好处很明显,我们可以在 proxy 里面做很多事情:例如本地文件缓存数据和网络数据的切换工作。甚至是和 CDN 使用其它的协议进行通信。我们这里假定预加载工作已经完成,看看播放器是怎么和 proxy 进行交互的。播放的时候会用 Proxy 提供的一个 localhost 的 url 进行播放,这样代理服务器会收到网络请求,把本地预加载的数据返回给播放器。播放器完全感知不到 proxy 模块、预加载模块的存在。播放器、预加载模块都是 Proxy 的 client,调用逻辑都是一样。图示说明如下:
下面逐步解释一下,数据的加载过程:
Client 发起 http 请求获取数据,箭头 1 所示
文件缓存如果存在所请求的数据则直接返回数据,箭头 2 所示
若本地文件缓存数据不够,则发起网络请求,向 CDN 请求数据,箭头 3 所示
获取网络数据,写入文件缓存,箭头 4 所示
返回请求的数据给 Client,箭头 2 所示
[](
)实现模块
[](
)预加载模块
确定了技术方案后,预加载模块还是有很多工作要做的。在列表网络数据解析完成后会触发视频预加载,首先会根据 url 生成 md5 值,然后去查看这个 md5 值对应的任务是否存在,如果存在则不会重复提交。生成任务后会提交到线程池,在后台线程进行处理。网络从 Wifi 切换到 3G 的时候,会把任务取消,防止消耗用户的数据流量。
预加载任务在线程池执行的时候,其流程是这样的:首先会获取一个本地代理的 url。然后发起 http 请求。Proxy 会收到 http 请求进行处理,开始做真正的数据预加载工作。预加载模块读取到指定的数据量后终止。到此,预加载的任务就已完成。流程图如下所示:
在用户快速滑动的时候,怎么能保证视频还能继续秒开呢?预加载模块对于每一个任务都会维护一个状态机,在 Fling 的时候会把划过的任务暂停下,把最新要显示的任务优先级提高,让其优先执行。
[](
)Proxy 模块
Proxy 内部有个 local 的 httpServer 负责拦截播放器和预加载模块的 http 请求。client 在请求时会带入 CDN 的 url,在本地缓存数据没有的时候会去 CDN 获取新鲜数据。因为有多个地方向 Proxy 请求数据,所以用线程池来处理多个 client 的连接很有必要,这样多个 client 可以并行,不会因为前面有 client 在请求而阻塞。文件缓存使用 LruDiskCache,在超过指定文件大小后,老的缓存文件会删除,这是一个在使用文件缓存时很容易忽视的问题。由于我们的场景视频是连续播放的,不存在 seek 的情况,所以文件缓存相对比较简单,不用考虑文件分段的情况。Proxy 内部对于同一个 url 会映射到一个 client,如果预加载和播放同时进行,数据只会有一份,不会去重复下载数据。再来一个 Proxy 内部构造示意图:
[](
)遇到的问题
在测试中发现,有的视频还是会播放很慢,仔细查看本地的确缓存了期望的数据大小,但是播放的时候还是有较长的等待时间,这种视频有个特点:moov box 在尾部。对于 moov 在尾部的视频,是整个文件都下载完成后才进行播放的,原因是 moov box 里面存了很多关键信息,前面分析 mp4 格式的时候有提到。对于这个问题有两个解法:
解法一:
服务端在进行转码的时候保证 moov 的头部在前面,发现 moov 位置不正确的视频服务端进行订正。
PS:查看 moov 在文件中的位置可以用 hex 文本编辑器打开,按字符搜索 moov 所在的位置即可,MAC 上面还可以使用[MediaParser](
) , 另外还可以用 ffmpeg 命令生成 moov 在头部或者尾部的 mp4 文件。
例如:
从 1.mp4 copy 一个文件,使其 moov 头在尾部
json
ffmpeg -i 1.mp4 -c copy -f mp4 output.mp4
从 1.mp4 copy 一个文件,使其 moov 头在头部:
json
ffmpeg -i 1.mp4 -c copy -f mp4 -movflags faststart output2.mp4
解法二
不用修改 moov box 的位置,而是在播放端进行处理,播放端需要检测流信息,如果 moov 前面没有,就去请求文件的尾部信息。具体就是:发起 HTTP MP4 请求,读取响应 body 的开头,如果发现 moov 在开头,就接着往下读 mdat。如果发现开头没有,先读到 mdat,马上 RESET 这个连接,然后通过 Range 头读取文件末尾数据,因为前面一个 HTTP 请求已经获取到了 Content-Length ,知道了 MP4 文件的整个大小,通过 Range 头读取部分文件尾部数据也是可以的。示意图如下
这个方案的缺点是:对于 moov box 在尾部的视频会多两次 http connection。
[](
)总结
本文介绍了常见的视频编码格式,视频封装格式,介绍了 moov 头信息对于视频播放的影响。随着对于播放流程的分析,我们找到了问题的切入点。简单说就是围绕着数据预加载展开,把网络请求数据的工作提前完成,播放的时候直接从缓存读取,而且后续的视频回看都是从缓存读取,不仅解决了视频初始化播放慢的问题,还解决了播放缓存问题,可以说是一箭双雕。Proxy 是这个方案的核心思想,本地 localhost 的 url 是一个关键纽带,视频预加载模块和播放器模块解耦彻底,换了播放器照样可以使用。到此为止,视频 feeds 秒开优化就已完成。上线后的数据来看视频打开速度在 800ms 左右。
回过头来,或许我们还可以更进一步,可以对预加载收到的数据进行验证,确保缓存了准确的信息,而不是固定的数值。还可以进行更加深度的优化,让用户观看视频的体验更加顺滑。
[](
)Android 音视频开发入门到精通
**以下音视频学习路线及学校笔记 PDF 电子书版可在[我的 GitHub](
)免费下载,记得给个 Star 哦~**
**快速入手通道:[(点这里)](
)群文件免费下载获取。**
[](
)一,初级入门篇:
=======================================================================
一 绘制图片
1. ImageView 绘制图片
2. SurfaceView 绘制图片
3. 自定义 View 绘制图片
二、AudioRecord API 详解
三、使用 AudioRecord 实现录音,并生成 wav
创建一个 AudioRecord 对象
初始化一个 buffer
开始录音
创建一个数据流,一边从 AudioRecord 中读取声音数据到初始化的 buffer,一边将 buffer 中数据导入数据流。
关闭数据流
停止录音
四、用 AudioTrack 播放 PCM 音频
1.AudioTrack 基本使用
2.AudioTrack 详解
3. AudioTrack 与 MediaPlayer 的对比
五、使用 Camera API 采集视频数据
1.预览 Camera 数据
2.取到 NV21 的数据回调
六、使用 MediaExtractor 和 MediaMuxer API 解析和封装 mp4 文件
1.MediaExtractor API 介绍
2.MediaMuxer API 介绍
3.使用情境
七. MediaCodec API 详解
1.MediaCodec 介绍
2.MediaCodec API 说明
3.MediaCodec 流控
由于文章篇幅受限,剩余内容过多,文中插图有限,下文只能截图目录展示:
[](
)二,中级进阶篇:
=======================================================================
Android OpenGL ES 开发(一): OpenGL ES 介绍
Android OpenGL ES 开发(二): OpenGL ES 环境搭建
Android OpenGL ES 开发(三): OpenGL ES 定义形状
Android OpenGL ES 开发(四): OpenGL ES 绘制形状
Android OpenGL ES 开发(五): OpenGL ES 使用投影和相机视图
Android OpenGL ES 开发(六): OpenGL ES 添加运动效果
Android OpenGL ES 开发(七): OpenGL ES 响应触摸事件
Android OpenGL ES 开发(八): OpenGL ES 着色器语言 GLSL
Android OpenGL ES 开发(九): OpenGL ES 纹理贴图
Android OpenGL ES 开发(十): 通过 GLES20 与着色器交互
使用 OpenGL 显示一张图片
GLSurfaceviw 绘制 Camera 预览画面及实现拍照
使用 OpenGL ES 完成视频的录制,并实现视频水印效果
[](
)高级探究篇:
=====================================================================
深入学习音视频编码,如 H.264,AAC,研究使用开源编解码库,如 x.264,JM 等
深入研究音视频相关的网络协议,如 rtmp,hls,以及封包格式,如:flv,mp4
深入学习一些音视频领域的开源项目,如 webrtc,ffmpeg,ijkplayer,librtmp 等等
将 ffmpeg 库移植到 Android 平台,结合上面积累的经验,编写一款简易的音视频播放器
将 x264 库移植到 Android 平台,结合上面积累的经验,完成视频数据 H264 软编功能
将 librtmp 库移植到 Android 平台,结合上面积累的经验,完成 Android RTMP 推流功能
音视频编解码技术
音视频编解码技术(一):MPEG-4/H.264 AVC 编解码标准
音视频编解码技术(二):AAC 音频编码技术
流媒体协议
评论