美文网首页程序员
FM987一期技术解析

FM987一期技术解析

作者: 肉饼屋 | 来源:发表于2019-12-17 12:40 被阅读0次

    前言

    在最近的一段时间里,我做了一个小项目叫做FM987。在我的需求规划里面,这个项目包含了点歌,音频直播,聊天等功能,目前只完成了一期的功能(点歌和音频直播)。如题所述,这篇文章主要介绍一期功能中,一些我觉得比较有趣也比较有分享价值的东西。

    整体方案概述

    为了方便大家更好的理解,所以我打算从整体架构开始介绍,然后再讲到具体细节。

    音频直播

    对于FM987来说,最核心的功能就是实时听歌功能,所以选择音频直播方案就很顺理成章了。

    最简单的架构图

    对于音频直播而言,最简单的架构图可能就是下面这张图了: 音频源上传音频,通过服务器转发给听众。


    直播基本结构图

    自定义音频源的直播

    在FM987的需求中,大家听到的歌曲是听众自己点歌的,并不像传统方案中,由客户端录音然后推流到服务器。所以为了满足点歌直播的需求,我们就需要在服务器实现一个自定义音频源,自定义音频源可以将用户点的歌变成音频数据发送听众。在我的实现方案中,将这个音频源称为Player,下面是加入Player后的架构图。


    Player的直播架构图

    更详细的架构图

    自定义音频源是FM987最核心的东西,也是最区别于其他直播方案的东西,其他的东西都是基于这个点进行拓展,包括如何点歌,如何获取音频文件,怎么解析文件等。下面是更详细的架构图,大家可以看下。


    整体架构图

    当然,从架构图上就看出,现在fm987的架构还不是很合理,例如player干了太多事情,可以尝试将一些东西抽离出来。

    网易云和QQ音乐的下载接口解析

    FM987可以让用户进行搜歌、点歌。我最初是选择了网易云、QQ音乐和虾米作为点歌的数据源,不过虾米接口的验证码机制让我放弃了它,所以现在只接入了网易云和QQ音乐,并且只有支持免费歌曲。不过这两家的开发肯定也知道有很多人会拉取他们的数据,所以他们在接口的防调用上做了一些保护措施,特别是获取下载地址接口。

    网易云的下载接口加密

    网易云的下载接口有两个参数:paramsencSecKey,例如:

    网易云下载接口参数
    看到这两个参数的值,可能大家就明白了,我为什么要特别介绍这个接口了。
    params的值的形成流程是这样的:
    params的形成
    这个加密方式应该是我见过最复杂的了,而且有几个点很有意思:
    1. Json序列化,正常这部分都是存到map中再序列化,而大部分map都是无序的,但是网易云的后台验证中,会要求几个参数必须按照一定的顺序进行序列化
    2. 随机生成的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有时效性。


    QQ音乐获取下载地址的流程

    基于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协议是一种很适合直播流的协议。当听众直播间后,我们只需要先给听众发送一个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();
    

    结尾

    在上面的技术解析中,我将一些我个人觉得有点意思的东西介绍下,知识点比较杂,也比较零散。很多东西也没有细讲,更多是提供一种思路,希望对大家有帮助吧。

    相关文章

      网友评论

        本文标题:FM987一期技术解析

        本文链接:https://www.haomeiwen.com/subject/bussnctx.html