最近在着手部署上线作的一个视频网站,当咱们部署到云服务器上后并开始测试视频观看并发量,发现了一个很严重的问题:带宽不足。9 或 10 我的同时观看视频的时候,就会出现有些用户加载不了视频的问题。
咱们的云服务器是 1块钱的腾讯云,自己带宽就很低了,因此确定没有足够大的带宽来支撑这么大的流量传输。这对于一个视频网站来讲,带宽这个问题是很是致命的。在不能改变带宽的条件下,因而咱们去找了一些方法来提升性能。html
咱们的视频网站使用的内置 Flash 播放器来播放视频,用户观看视频的时候是直接彻底加载整个视频的(无论你看了多久),从一开始播放就开始加载,而且并不会由于用户暂停而暂停加载, 它是一直持续加载直到加载彻底的。对于绝大多数用户来讲,他们不必定会把视频看完,若是是加载一个小视频,那尚未什么大问题,但若是是加载一个大视频的话,这就会浪费的大量的流量,而且加载过程会持续占用带宽,使得用户量多的时候,视频加载就会出现问题。java
了解到这个问题以后,咱们去看了别人的视频网站是如何撑起高用户量的,在视频播放的时候,咱们发现它们并非一开始就彻底加载视频的,而是一段段的加载,去搜索以后发现这是一种切片的技术,用于控制流量传输。具体的切片的原理可参看 http://www.cnblogs.com/flash3d/archive/2013/11/02/3403109.html。
了解了切片技术以后,咱们因而就开始在咱们项目中应用切片的技术,咱们使用的是 ffmpeg 来对视频进行切片。方法就是在程序中调用 ffmpeg 程序,而后调用切片命令对咱们的视频进行切片,生成 m3u8 文件和 ts 文件,而后使用 flash 播放器播放,可以看到的确可以一段段的加载视频。
修改到这一步,这彷佛解决了咱们的问题,可是新的问题又出现了,咱们发现当咱们对大视频进行切片的时候,服务器的内存会占用很大,至于为何会占用那个大,咱们猜测多是由于对视频切片时,ffmpeg 把整个视频加载到内存,因此致使内存占用高。当同时对多个视频进行切片的时候,服务器就炸掉了。因而咱们又寻求新的方法去解决。
跨域
由于没想到好的方法去解决本地切片内存占用问题,因而咱们使用了新的途径去存储播放视频,就是使用云端存储来存储视频,咱们选用的是七牛云服务器来存储。它也提供了不一样语音的 SDK 供开发者参考。
使用七牛云,我在个人 Java Web 项目里面导进必须的 4 个包,以及编写了上传视频并进行切片预处理的工具类。刚开始使用的时候也遇到许多问题。浏览器
一开始,咱们对任何格式的视频都调用同一个切片命令,觉得会生成同一种格式的视频文件。可是当咱们上传的 MP4 格式的视频,切片上传后,直接在浏览器输入外链(文件的访问连接),此时可以正常播放;可是上传 avi 或者 flv 格式的视频,上传切片后,直接输入外链会变成下载文件。
当时一直想不通这个问题的缘由,由于明明都是调用了同一个命令切片,按理来讲应该格式是同样的,可是却出现不一样的行为。后来经过七牛云的问答平台寻求解决方案,才发现若是在上传的时候,没有使用 saveas 参数对结果另存为 xxx.m3u8 格式,他仍是任然会以原有的格式去保存。因此基于浏览器对不一样视频格式的支持,对 mp四、avi、flv等格式的视频则出现不一样的效果服务器
在咱们解决完视频上传的问题以后,在播放器经过外链来播放视频的时候,发现出现跨域被拒绝的问题 (ERROR:HLSError(code/url/msg)=1tp:Cannot load M3U8: crossdomain access denied:Error #2048),google 了问题,发现原来Flash 播放器在加载跨域视频时,会先去加载云端的 corssdomain.xml 文件,而后判断是否被容许加载。
解决方法:须要在七牛云端上传 crossdomian.xml 文件并发
<cross-domain-policy>
<allow-access-from domain="*"/>
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>
基于七牛云的 API,写了一个上传视频并切片的接口,可供参考;若有错 ,可一块儿讨论
该接口的配置信息采用配置文件 video.properties 来加载,具体的配置文件配置内容为:cors
access_key=your access key
secert_key=your secert key
bucketname=你的存储空间
pipeline=你的多媒体处理队列名
fops=avthumb/m3u8/noDomain/1/vb/500k/t/120(这是切片命令)如果要作其余处理,请参照七牛云 SDK
domain=你的七牛云映射域名
public class QiNiuUtil {
private static String DEFAULT_PROPERTIES = "video.properties";
private static Properties properties = new Properties();
static {
String path = QiNiuUtil.class.getResource("/").toString();
path = path.substring(6, path.length() - 8) + DEFAULT_PROPERTIES;
System.out.println(path);
try {
FileInputStream fileInputStream = new FileInputStream(path);
properties.load(fileInputStream);
System.out.println(properties.toString());
} catch (IOException e) {
System.out.println("配置文件不存在,加载配置文件失败");
}
}
public static String domian = properties.getProperty("domain");
private static String ACCESS_KEY = properties.getProperty("access_key");
private static String SECRET_KEY = properties.getProperty("secert_key");
// 要上传的空间
private static String bucketname = properties.getProperty("bucketname");
// 设置切片操做参数
private static String fops =properties.getProperty("fops");
// 设置转码的队列
private static String pipeline = properties.getProperty("pipeline");
//密钥配置
private static Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
//建立上传对象
private static UploadManager uploadManager = new UploadManager();
//上传策略中设置persistentOps字段和persistentPipeline字段
public static String getUpToken(String pfops){
return auth.uploadToken(bucketname,null,3600,new StringMap()
.putNotEmpty("persistentOps", pfops)
.putNotEmpty("persistentPipeline", pipeline), true);
}
public static boolean upload(byte[] data, String key) throws IOException{
Response res = null;
try {
// 调用put方法上传
// 指定文件以 m3u8 格式另存
String urlbase64 = UrlSafeBase64.encodeToString(bucketname + ":" + key + ".m3u8");
res = uploadManager.put(data, key, getUpToken(fops + "|saveas/"+ urlbase64));
//打印返回的信息
System.out.println(res.bodyString());
} catch (QiniuException e) {
Response r = e.response;
// 请求失败时打印的异常的信息
System.out.println(r.toString());
try {
//响应的文本信息
System.out.println(r.bodyString());
} catch (QiniuException e1) {
//ignore
}
}
return res.isOK(); }}