Web 端声纹识别 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ssttm169

Web 端声纹识别

  •  
  •   ssttm169
    ssttm169 2018 年 6 月 4 日 2583 次点击
    这是一个创建于 2882 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近做一个微信的口令红包的功能,准备都要投入使用了, 老板突然发愁,他说 现在的羊毛党这么猖狂,一不小心,10 万的推广红包,会不会两天就挨刷完了? ....那我们能否做一个功能校验一下是否同一个人来领取红包,不就得了吗? 他一拍脑袋,接着说,Tom 你给我们做一个声纹识别吧!


    说干就干,在寻找 声纹识别服务商,发现什么科大讯飞,还什么 BAT 等许多大厂都没有支持 Web 端的,后来找到一个不知名的小厂。。

    具体的流程如下:

    具体流程


    声纹注册用户(最终效果图)

    注册效果图


    声纹登录(最终效果图)

    登陆效果图


    上传文件识别:

    上传文件识别


    pm2 线程

    pm2 线程

    服务端

    因为声纹识别服务商 不能直接使用客户端直接调用 和 音频不支持的问题,要开发自己的服务端来对接。

    技术栈 koa + co-wecaht-api + mysql + ffmpeg + pm2 + knex

    注:因服务商不支持微信 amr 文件, 要用 ffmpeg 把微信的音频 amr 文件转码成 wav。

    以下是一些相关的代码,,开撸。。

    微信 jssdk 开发 如果你微信 API 这一块已经很熟悉了,跳到下一节

    获取微信 token

    var api = await new WechatAPI( config.appid, config.appsecret, async () => { // 传入一个获取全局 token 的方法 var txt = await fs.readFile("./token/access_token.txt", "utf8"); return JSON.parse(txt); }, async token => { // 请将 token 存储到全局,跨进程、跨机器级别的全局,比如写到数据库、redis 等 // 这样才能在 cluster 模式及多机情况下使用,以下为写入到文件的示例 await fs.writeFile("./token/access_token.txt", JSON.stringify(token)); } ); 

    注:如果报读取不了 token 文件,就手动在相应的目录,新建的文本文件, 比如 access_token.txt


    获取微信签名

    var jsapi_ticket = await api.getLatestTicket(); let nonce_str = 'abcdefg'; // 密钥,字符串任意,可以随机生成 let timestamp = parseInt(new Date().getTime() / 1000) + ''; // 时间戳 let url = ctx.request.body.url; // 使用接口的 url 链接,不包含#后的内容 let str = 'jsapi_ticket=' + jsapi_ticket.ticket + '&nOncestr=' + nonce_str + '&timestamp=' + timestamp + '&url=' + url; let signature = sha1(str); ctx.body = { appId: config.appid, timestamp: timestamp, nonceStr: nonce_str, signature: signature } 

    跨域请求

    const Koa = require("koa"); const app = new Koa(); const cors = require("koa-cors"); ..... app.use( cors({ origin: "http://www.xxxx.com", maxAge: 5, credentials: true, allowMethods: ["OPTIONS", "GET", "POST", "DELETE"], allowHeaders: ['Content-Type', 'Accept'] }) ); 

    ffmpeg 转码

    const ffmpeg = require('fluent-ffmpeg'); .... var command = ffmpeg(_delPath.amr) .audioBitrate('16k') //16k 音频采样率 .audioFrequency(16000) //16 比特音频信号 .audioQuality(10) //音频质量 .on('end', function() { console.log('file has been converted succesfully'); resolve(); }) .on('error', function(err) { reject(err.message) console.log('an error happened: ' + err.message); }) .save(_delPath.fix); 

    提交声纹服务器

    const rp = require("request-promise"); ..... var vprData = { method: "POST", url: "http://www.xxxx.com", headers: { "cache-control": "no-cache", "x-udid": "xxxxxx", "x-session-key": "xxxx", "x-task-config": "xxxxxx", "x-request-date": "xxxxxx", "x-sdk-version": "5.1", "x-app-key": "xxxxxxx" }, formData: { // Like <input type="file" name="file"> file: { value: fs.createReadStream(soundData.path), options: { filename: soundData.name, contentType: soundData.type //mp3 = audio/mpeg, wav = audio/wav } } } }; var xml = await rp(vprData); //xml to json var resJson = {}; var parseString = require('xml2js').parseString; await new Promise((resolve, reject) => { parseString(xml.toString(), async (err, result) => { resJson = result.ResponseInfo; //do something resolve(); }); }); 



    客户端

    技术栈 vue + vue-router + axios。

    去掉微信 长按 弹出复制的按钮

    mounted() { document.Oncontextmenu= function(e) { e.preventDefault(); }; //初始化 微信 jssdk vm.wx_init(); } 

    获取微信签名,注册事件

    wx.config({ debug: false, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。 appId: res.appId, // 必填,公众号的唯一标识 timestamp: res.timestamp, // 必填,生成签名的时间戳 nonceStr: res.nonceStr, // 必填,生成签名的随机串 signature: res.signature, // 必填,签名,见附录 1 jsApiList: [ "onMenuShareTimeline", "onMenuShareAppMessage", "uploadVoice", "startRecord", "playVoice", "stopRecord", "onVoicePlayEnd" ] // 必填,需要使用的 JS 接口列表,所有 JS 接口列表见附录 2 }); 

    提前提示用户授权录音功能, 为了避免 正式开始录音时,同时提示授权,此时录音功能状态已经失控。

    if (!localStorage.rainAllowRecord || localStorage.rainAllowRecord !== "true" ) { wx.startRecord({ success: function() { localStorage.rainAllowRecord = "true"; wx.stopRecord(); }, cancel: function() { alert("用户拒绝授权录音"); } }); } 

    好了,talk is cheap, show you the code.

    Github 源代码在此, 给星星的人都很美~

    yimity
        1
    yimity  
       2018 年 6 月 4 日
    我想知道的是,正确率有多少。
    不过还是要赞美一下。很棒。
    takato
        2
    takato   div class="badges">   2018 年 6 月 4 日 via iPhone
    现在的情况是:敢用模型对方就敢用 GAN
    ∠( 」∠)_
    paparika
        3
    paparika  
       2018 年 6 月 4 日
    领过之后可以在后台记录 ta 的微信 id 防止重复领吧,还是我理解错了
    EchoChan
        4
    EchoChan  
       2018 年 6 月 4 日
    能识别合成的声音?
    1stPLACE
        5
    1stPLACE  
       2018 年 6 月 4 日
    我记得某个区块链交易所的实名认证就是用了微信端网页语音认证。
    SingeeKing
        6
    SingeeKing  
    PRO
       2018 年 6 月 4 日
    @paparika #3 意思应该是防止羊毛党「一个人多个微信账号领取」
    ssttm169
        7
    ssttm169  
    OP
       2018 年 6 月 4 日
    @1stPLACE 是哪个区块链交易所?还记得不? 发过来我体验一下。。~~谢谢。
    ssttm169
        8
    ssttm169  
    OP
       2018 年 6 月 4 日
    @yimity 准确率不是很高, 我现在设定是录音时间是 5 秒,,如果录音时间长一点的话就准确率就高一点。
    ssttm169
        9
    ssttm169  
    OP
       2018 年 6 月 4 日
    @takato GAN,没有研究过这个,谢谢你提醒。。
    ssttm169
        10
    ssttm169  
    OP
       2018 年 6 月 4 日
    @paparika 我设置每个微信 ID 只能领取一个红包,,但是专业的羊毛党,一般是一个控制成千上万的 ID,但如果有声纹识别,他就没戏了,不过这个功能,还在实验当中。。。
    ssttm169
        11
    ssttm169  
    OP
       2018 年 6 月 4 日
    @EchoChan 估计不能,,现在技术还没有这么先进吧~~
    takato
        12
    takato  
       2018 年 6 月 4 日
    @ssttm169 相关资料可以通过搜索这个关键词找到 Generative adversarial network
    EchoChan
        13
    EchoChan  
       2018 年 6 月 4 日
    @ssttm169 如果不能识别合成的声音,那就防不住专业的羊毛党了。
    ssttm169
        14
    ssttm169  
    OP
       2018 年 6 月 4 日
    @EchoChan 恩,,是的,这个功能还在实验阶段,,还没有正式投入使用呢~~
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2807 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 68ms UTC 13:20 PVG 21:20 LAX 06:20 JFK 09:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86