Android短视频边下边播详解

短视频作为一种常见的富媒体信息载体已经在移动互联网上得到非常普遍的应用,比如Snapchat、微信、手Q日迹等。由于手机网络流量珍贵且带宽有限,应用通常不会直接在线播放视频,而是把视频完整下载到本地后再进行播放,但是下载完整视频需要时间,尤其是视频较大或在网络较差的情况下等待下载的时间就会更长,容易影响用户体验。于是我们想到了边下载边播放,既不浪费流量,也不占用等待时间。下面我们将分别对它的几个实现要点进行详细讲述。

【视频格式】    

 想要实现边下边播,首先我们需要了解一下视频文件格式。一般情况下,视频文件结构如下所示:

内容元素主要包括:

  • 图像(image)
  • 音频(audio)
  • 元信息(metadata) 编码格式(codec)主要包括:
  • video:H.264、H.265、…
  • audio:AAC、HE-AAC、… 容器封装(container)主要包括:
  • MP4、FLV、AVI、MOV、RMVB、…

播放器在播放视频文件时,之所以知道该怎么去解码,以什么样的时间间隔去显示每一帧,是因为metadata记录了当前视频文件的图像尺寸、编码格式、帧率、码率等等信息,播放器通过解析metadata得到了这些信息,才能控制视频的显示,也就是说播放器要先解析完metadata才会开始播放。  

我们拿MP4作为例子来说明,不同容器的封装在数据存储上会存在一些差异,MP4视频文件结构如下所示:

它对应的metadata信息称为moov,mdat包含了音频和视频数据。MP4在实际制作中,moov有可能被放到了mdat后面,所以我们要保证制作出来的MP4的moov是放置在mdat前面的,这样才可以实现边下边播功能。如果不是这样,可以用FFmpeg的faststart命令(ffmpeg -i input.mp4 -movflags +faststart output.mp4)处理一下。        另外值得一提的是,如果moov比较大,播放器需要较多的时间去解析,所以在播放之前可能会出现较长的缓冲时间,特别是视频文件较大的情况下,所以现在有些点播网站会采用每段mdat都有自己独立的metadata的封装方式,这样就可实现渐进式下载和快速缓冲的效果。

【本地代理】

在确保视频文件的metadata在头部后,我们只要完整下载metadata,再加上少许音视频数据,就可以开始播放视频了,那么如何实现“边下”呢?我们都知道,Android平台上要播放视频,最基本的方式就是实例化一个MediaPlayer, 将视频的URL通过setDataSource()设置给播放器,之后调用prepare()或prepareAsync()和start()就可以开始播放视频了。于是我们很容易想到将MediaPlayer的视频源设置为本地文件,然后通过子线程不断将下载数据追加到该文件,但笔者经过验证,这种做法会经常导致MediaPlayer各种报错,无法顺利播放。    

 经过深入调研,很遗憾MediaPlayer并没有提供类似可以拦截URL或文件流的API可以让我们将视频文件保存到本地(然而,iOS视频播放器有提供了类似接口)。所以我们换了一个思路,就是当播放器请求播放远程视频文件时,我们将远程URL篡改成本地URL,播放器播放视频时不再是直接访问远程视频文件,而是先访问本地代理,本地代理再去下载远程视频,下载多少就给播放器输送多少,这样就实现了边下边播,我们将这种做法称之为本地代理服务器。

【数据流程】 播放器请求本地代理服务器的数据流程如下图:

1、播放器播放之前,先把网络视频的远程url替换成本地的url(类似http://127.0.0.1/xxx);

2、播放器开始播放时将本地url请求发给proxy server;

3、proxy server根据本地url在本地缓存中查找是否存在该视频,如果存在则直接跳到步骤7,如果不存在,则进入步骤4;

4、proxy server根据视频远程url向视频server请求下载视频数据;

5、视频server返回给proxy server视频数据;

6、proxy server将返回的视频数据缓存到本地,并且处理其他业务逻辑;

7、proxy server将视频数据返回给播放器,播放器开始播放。 比起播放器直接播放网络视频,Proxy的做法使得视频的播放和下载在一定程度上变得可控,除了能够提供边下边播能力以外,还可以增加额外的视频相关业务逻辑,比如缓存、预下载、防盗链等等。

【技术架构】

  Proxy Server的http服务器实现可以参考一些开源项目如NanoHttpd,但如果想自己实现也不会很难,我们一起来看下它的技术架构,如下所示:

  • proxy server为播放器提供http服务,是一个本地http服务器,内部通过线程轮询监听播放器请求,可以支持get和range header的请求和响应,range 主要用来支持视频断点续传或播放拖拽功能;
  • 由于播放器可能会有多个请求或多个播放器同时请求,所以需要线程池来支持并发请求;
  • 当播放器发起视频下载请求,proxy首先会根据url在本地缓存查找对应的视频文件,如果找到就直接返回数据给播放器,如果没有找到,proxy会向视频server发起http请求;
  • 由于SD卡空间有限,下载后的视频采用LRU算法进行淘汰。

【缓存淘汰】

关于Proxy Server下载的视频缓存路径,由于手机内部存储空间有限,视频又比较大,不建议内部存储,所以可以放到SD卡上的路径/sdcard/Android/data//cache下,并且为了唯一标识文件,可以使用MD5(url)作为文件名。选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现卸载应用之后手机上还有残留数据的问题。      

 由于SD卡存储空间有限,下载的视频如果不清除很快就会爆满。或许我们可以在达到爆满之前给用户提醒要手动清除,但用户自己可能也很难做出正确的淘汰判断,而且经常提醒会让用户容易厌烦,所以我们可以使用LRU(Least Recently Used,近期最少使用算法)来实现视频的自动清除,它是一种较为常见的缓存淘汰算法,其核心思想是“如果数据最近经常被访问过,那么将来被访问的几率也更高,反之就应该被淘汰”。缓存淘汰的逻辑流程如下所示:

  • 触发时机:因缓存淘汰需要遍历所有视频并排序,视频数量多会比 较耗IO和CPU,所以可以选择应用进入后台再异步进行;
  • 淘汰条件:视频总数超过300个,视频总大小超过500M,视频过期 (未使用超过1周),具体数值可根据需求动态配置。

【安全设计】    

手机连接网络,实际上是通过运营商网关(SGSN和GGSN)做一个网络地址转换(Network Address Translation,NAT),实现内网IP和外网IP的映射,从而连接上了Internet,所以手机移动网络实际上是一个大型的“局域网”。

 由于Proxy Server本质是一个http服务器,启动时会随机开放一个端口,如此一来,就相当于将本地服务端口暴露给整个手机网络,在这种情况下,黑客可以在这个大“局域网”内扫描出开放的端口,然后再伪造非法url模拟请求。臭名昭著的“WormHole虫洞漏洞”就是该漏洞的典型,其根本原因就是没有对请求进行限制和验证,而本身又提供了敏感服务,让黑客有机可乘。

明白了攻击原理,我们就知道如何预防,措施如下:

1、对请求url进行规则限制,只接受特定的url请求;

2、对请求者进行身份验证,只接受播放器发起的请求,这里使用了消息摘要算法HMAC-MD5或HMAC-SHA1,并对其稍作改造:

1).播放器请求时,生成一个随机数random_key;

2).将random_key作为密钥,url和timestamp作为输入,使用HMAC-MD5/SHA1生成一个hash值sign,然后将该字符串追加到url后面,向proxy发起请求,如下图所示:

3).proxy收到请求后,先验证timestamp是否超过时间限制,防止重放攻击,接着根据random_key(本地获取)、url、timestamp使用同样的签名算法也生成一个签名字符串sign,然后和请求的sign比对,如果一致,则认为是授权的,否则就拒绝请求,如下图所示:

综上所述,为了实现短视频的边下边播功能,本文首先分析了实现“边播”的要点在于视频的Metadata要在头部,然后分析了“边下”的实现方案,提出本地代理并对其架构进行说明,接着介绍如何使用LRU对缓存视频进行淘汰,最后针对本地代理可能存在的安全性问题进行了思考并给出解决方案。通过文章全篇分析可以看出,边下边播其实是“短视频类应用”播放短视频的较好的通用解决方案,因此笔者后续会将本文描述的解决方案的代码封装成组件提供出来,供类似场景快速复用。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏何俊林

Android Multimedia框架总结(二)MediaPlayer框架及播放网络视频案例

前言:前面一篇我们介绍MediaPlayer相关方法,有人说,没有实际例子,看得不是很明白,今天在分析MediaPlayer时,顺带一个播放网络视频例子。可以自...

2039
来自专栏IMWeb前端团队

基于MQTT的实时日志系统

MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网...

28010
来自专栏HBStream流媒体与音视频技术

MP4大文件虚拟HLS分片技术,避免服务器大量文件碎片

对于大家经常见到和使用到的普通MP4来说,作为电影、电视文件的存储容器,是很好的,不过对于流媒体点播来说,最大的缺点就是它的媒体信息和关键帧索引都集中存放在mo...

66212
来自专栏Hongten

网页上播放视频的免费的播放器_CKPlayer

今天在工作的过程中遇到一个功能:在网页中加入视频播放器,类似于我们经常看到的优酷,爱奇艺等视频网站的功能。

5301
来自专栏Youngxj

bootstrap简洁居中毛玻璃登录源码

3035
来自专栏互联网技术栈

如何提高程序员人效?代码生成工具/框架

一款人气很旺国外的基于模板的dotnet代码生成器 官方网站:http://www.codesmithtools.com 官方论坛:http://forum...

224
来自专栏开源优测

接口测试 | 21 基于flask弄个restful API服务出来

概述 上篇我们很简单的分享了如何基于flask搞一个支持http GET\POST\HEAD\DELETE方法的服务,大家可以根据这个简单的实例进行扩展。 下面...

3289
来自专栏QQ音乐技术团队的专栏

​PNG图片压缩对比分析

随着版本的迭代,业务的增加,QQ音乐apk的大小已经超过25M,其中res目录占用的大小超过5.5M,所以提出了对安装包进行瘦身的技术需求。

1.2K7
来自专栏DeveWork

移除WordPress 管理后台的主题编辑功能

出于安全考虑,如果你的WordPress 是多人使用的(比如说一个团队博客,多用户管理)。那么为防止一些小白胡乱修改后台导致网页问题,可以移除WordPress...

1816
来自专栏PHP在线

yii常用操作

yii 数据save后得到插入id $post->save(); //得到上次插入的Insert id $id = $post->attributes['id'...

2857

扫码关注云+社区