Web 端声纹识别

2018-06-04 10:40:21 +08:00
 ssttm169

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


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

具体的流程如下:


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


声纹登录(最终效果图)


上传文件识别:


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 源代码在此, 给星星的人都很美~

2179 次点击
所在节点    前端开发
14 条回复
yimity
2018-06-04 10:55:49 +08:00
我想知道的是,正确率有多少。
不过还是要赞美一下。很棒。
takato
2018-06-04 11:09:43 +08:00
现在的情况是:敢用模型对方就敢用 GAN
∠( ᐛ 」∠)_
paparika
2018-06-04 11:39:02 +08:00
领过之后可以在后台记录 ta 的微信 id 防止重复领吧,还是我理解错了
EchoChan
2018-06-04 12:32:24 +08:00
能识别合成的声音?
1stPLACE
2018-06-04 12:32:36 +08:00
我记得某个区块链交易所的实名认证就是用了微信端网页语音认证。
SingeeKing
2018-06-04 16:25:07 +08:00
@paparika #3 意思应该是防止羊毛党「一个人多个微信账号领取」
ssttm169
2018-06-04 16:45:06 +08:00
@1stPLACE 是哪个区块链交易所?还记得不? 发过来我体验一下。。~~谢谢。
ssttm169
2018-06-04 16:46:26 +08:00
@yimity 准确率不是很高, 我现在设定是录音时间是 5 秒,,如果录音时间长一点的话就准确率就高一点。
ssttm169
2018-06-04 16:47:36 +08:00
@takato GAN,没有研究过这个,谢谢你提醒。。
ssttm169
2018-06-04 16:49:21 +08:00
@paparika 我设置每个微信 ID 只能领取一个红包,,但是专业的羊毛党,一般是一个控制成千上万的 ID,但如果有声纹识别,他就没戏了,不过这个功能,还在实验当中。。。
ssttm169
2018-06-04 16:50:08 +08:00
@EchoChan 估计不能,,现在技术还没有这么先进吧~~
takato
2018-06-04 16:56:28 +08:00
@ssttm169 相关资料可以通过搜索这个关键词找到 Generative adversarial network
EchoChan
2018-06-04 17:12:56 +08:00
@ssttm169 如果不能识别合成的声音,那就防不住专业的羊毛党了。
ssttm169
2018-06-04 17:17:17 +08:00
@EchoChan 恩,,是的,这个功能还在实验阶段,,还没有正式投入使用呢~~

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/460146

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX