前言
在最近的一段时间里,我做了一个小项目叫做FM987。在我的需求规划里面,这个项目包含了点歌,音频直播,聊天等功能,目前只完成了一期的功能(点歌和音频直播)。如题所述,这篇文章主要介绍一期功能中,一些我觉得比较有趣也比较有分享价值的东西。
整体方案概述
为了方便大家更好的理解,所以我打算从整体架构开始介绍,然后再讲到具体细节。
音频直播
对于FM987来说,最核心的功能就是实时听歌功能,所以选择音频直播方案就很顺理成章了。
最简单的架构图
对于音频直播而言,最简单的架构图可能就是下面这张图了: 音频源上传音频,通过服务器转发给听众。
自定义音频源的直播
在FM987的需求中,大家听到的歌曲是听众自己点歌的,并不像传统方案中,由客户端录音然后推流到服务器。所以为了满足点歌直播的需求,我们就需要在服务器实现一个自定义音频源,自定义音频源可以将用户点的歌变成音频数据发送听众。在我的实现方案中,将这个音频源称为Player,下面是加入Player后的架构图。
更详细的架构图
自定义音频源是FM987最核心的东西,也是最区别于其他直播方案的东西,其他的东西都是基于这个点进行拓展,包括如何点歌,如何获取音频文件,怎么解析文件等。下面是更详细的架构图,大家可以看下。
当然,从架构图上就看出,现在fm987的架构还不是很合理,例如player干了太多事情,可以尝试将一些东西抽离出来。
网易云和QQ音乐的下载接口解析
FM987可以让用户进行搜歌、点歌。我最初是选择了网易云、QQ音乐和虾米作为点歌的数据源,不过虾米接口的验证码机制让我放弃了它,所以现在只接入了网易云和QQ音乐,并且只有支持免费歌曲。不过这两家的开发肯定也知道有很多人会拉取他们的数据,所以他们在接口的防调用上做了一些保护措施,特别是获取下载地址接口。
网易云的下载接口加密
网易云的下载接口有两个参数:params和encSecKey,例如:
看到这两个参数的值,可能大家就明白了,我为什么要特别介绍这个接口了。
params的值的形成流程是这样的:
这个加密方式应该是我见过最复杂的了,而且有几个点很有意思:
- Json序列化,正常这部分都是存到map中再序列化,而大部分map都是无序的,但是网易云的后台验证中,会要求几个参数必须按照一定的顺序进行序列化
- 随机生成的16位key,如果不知道这个值,后端是无法对参数进行解密的,而encSecKey就是这个key加密后的值
encSecKey的形成过程:网上有人说这个值是通过对key做rsa算法加密后得到了,但是我用常规的rsa加密后无法通过验证,所以我觉得这个rsa算法可能是网易云自己设计的,当然也可能是我对于rsa的了解不够吧。下面附上java的加密代码:
private static String neteaseRsaEncrypt(String src, String exponentStr, String modulusStr) throws Exception {
byte[] bytes = src.getBytes("UTF-8");
ArrayUtils.reverse(bytes);
String tempText = HexUtils.toHexString(bytes);
BigInteger biText = new BigInteger(tempText, 16);
BigInteger biExponent = new BigInteger(exponentStr, 16);
BigInteger biModulus = new BigInteger(modulusStr, 16);
BigInteger bitRet = biText.modPow(biExponent, biModulus);
return neteaseFill(bitRet.toString(16), 256);
}
private static String neteaseFill(String str, int length) {
StringBuilder stringBuilder = new StringBuilder(str);
while (stringBuilder.length() < length) {
stringBuilder.insert(0, "0");
}
return stringBuilder.toString();
}
QQ音乐的下载接口
QQ音乐他们的下载接口没有像网易云那么复杂,只是增加了一个获取token的过程,并且这个token有时效性。
基于flv的音频直播方案
在这个直播时代,直播方案有很多种,下面我会介绍下选择flv的原因,并且怎么实现他的流服务器的。
各种直播方案的对比
传输协议 | 播放器 | 延迟 |
---|---|---|
RTMP | Flash | 1s |
HTTP-FLV | Video | 1s |
HLS | Video | 10S |
RTMP对我来说太重了,没必要。而HLS的延迟我在几年前刚接触的时候就有体会了,直接就被否决掉了。所以就剩下http-flv,而且B站的flv.js挺出名的,趁这个机会也可以用用看。
HTTP-FLV的实现方案
HTTP-FLV分为http和flv,http是获取数据的方式,flv是数据的格式,所以下面先介绍下flv协议,然后再介绍如何通过http来获取flv的流数据。
flv协议
我这里就不介绍flv的背景,有兴趣的同学可以自行百科下,主要说下flv协议。
从flv的协议就能明白,flv协议是一种很适合直播流的协议。当听众直播间后,我们只需要先给听众发送一个flv header的数据块,然后就可以只发送tag数据块了,而每一个tag都是相对独立的,可以让我们对于播放的内容有很大的灵活性。在FM987中,我只需要将音频文件转码为flv文件,然后将文件解析为一个个tag数据块,就可以根据需要发送给听众进行播放了。
http流
在http1.1协议中,有一种叫做chunked transter encoding的传输编码方式,它允许服务器将要发送的数据分成多个部分,然后逐个发送给客户端。这个传输方式和flv简直是天作之合。我们可以每一个tag的数据都为一个块发送给客户端.下面是我发送数据的代码
FLVPacket packet = flvTag.getFlvPacket();
byte[] data = packet.getData();
byte[] chunkedHeader = (Integer.toHexString(data.length) + "\r\n").getBytes();
byte[] chunkedFooter = "\r\n".getBytes();
socket.getOutputStream().write(chunkedHeader);
socket.getOutputStream().write(data);
socket.getOutputStream().write(chunkedFooter);
socket.getOutputStream().flush();
结尾
在上面的技术解析中,我将一些我个人觉得有点意思的东西介绍下,知识点比较杂,也比较零散。很多东西也没有细讲,更多是提供一种思路,希望对大家有帮助吧。