- app name: 声纹云系统用户名
- app access key: 声纹云访问密钥
- app secret key: 声纹云加密密钥
声纹识别是一种经济、可靠、简便和安全的身份识别方式,语音波形反映说话人生理和行为特征的语音参数,自动识别说话人身份的技术,在安全性上可与其他生物识别技术(指纹、掌形和虹膜)相媲美,且只需麦克风即可,数据采集极为方便,造价低廉。
声纹识别在公共安全监控,刑事侦查音谱分析,司法声纹证据,银行、互联网金融贷款身份/黑名单识别,社会保险发放身份确认,移动支付身份验证,智能硬件用户识别个性化服务等均可应用
声纹识别服务要获得期望的效果,须正确录入语音。
声纹识别的操作可分为两种:注册声纹(声音特征建模)和声纹对比。有效的声纹必须准确、纯粹、完全地反映说话人的声音特种,因此声纹录入时必须做到以下几点:
1. 说话人的有效语音时长足够,为了能让机器完全掌握声音特征,对于不同的模型,录音的有效时长有着不同的标准,详情请查看语音标准
2. 说话人语音需保持纯粹,即录入时需避免混入其他人的声音或过大的环境噪音(如喇叭声、马达声等),虽然Speakin在算法上进行了优化处理与提取,但是更加纯粹的语音录入,有助于声纹识别的准确性。
3. 说话人的讲话方式需保持正常,不可刻意进行变声、拖音、语速过度变化、声音强度过度变化等,这些行为均为导致声纹特征的改变,不利于声纹识别的准确性。
1. SpeakIn声纹云开放平台提供了不同语言版本的SDK下载,根据用户需求集成SDK后即可使用声纹云服务。
2. 为了正常使用SDK,必须在声纹云平台注册账号并认证为开发者,通过开发者账号创建应用申请APP ID使用。
3. SpeakIn提供公有云服务,需连接网络,如需搭建私有云,请与我们联系。
1. 声纹云App ID 是SDK声纹识别服务的必要秘钥。
2. 声纹云App ID 影响着声纹模型的使用,根据不同场景(语音时常,环境噪音,数据或自由文本,采样率等)及用户需求,每个App ID将被分配一个或多个算法模型。 ID使用。
声纹模型指不同的语音密码类型。
根据不同场景(语音时常,环境噪音,数字或自由文本,采样率等)及用户需求,目前SpeakIn声纹云提供4类模型:数字模型(数字为随机数字串)、短自由文本模型(任意中文短语)、短固定文本模型(固定的中文短语)、长自由文本模型(任意中文长语音)。
注册与验证的声纹模型需一致,才能进行正常识别。
1. 设置声纹用户,通过麦克风录入语音,进行声纹注册。
2. 1:1声纹验证,声纹注册完成后,再录入新语音,即可使用新语音对原注册语音进行对比,得出声纹是否一致的结果。
3. 1:N声纹识别,声纹注册完成后,再录入新语音,即可使用新语音对复数的语音进行对比,找出匹配的声纹。
4. 录入语音需满足一定标准才可得到高识别率,具体系数请参考语音标准
5. 需确保注册与验证的声纹模型一致,例如注册为数字模型,验证时需选择数字模型验证。
使用声纹云服务前必须要有:
声纹云系统用作比较/查询/注册等一些业务请求
声纹云存储则用来上传/下载文件
app和bucket都可以通过bd@speakin.mobi邮箱申请
上传文件后,声纹云会返回一个文件key作为标识,当注册/验证时使用这个key标识来代表文件
使用音频文件来注册用户,我们会把音频文件的特征和用户关联
校验用户的声纹特征值
服务端上传声纹文件验证流程
客户端上传声纹文件验证流程
服务端上传声纹文件验证流程
客户端上传声纹文件验证流程
go get -u github.com/speakin/sdk_go
加载sdk包
import sdk "github.com/speakin/sdk_go"
加载openapi
import "github.com/speakin/sdk_go/openapi"
初始化客户端
client := sdk.NewClient(
"your_access_key", // app access key
"your_secret_key", // app secret key
"your_bucket_access_key", // bucket access key
"your_bucket_secret_key", // bucket secret key
)
package main
import (
"context"
"fmt"
sdk "github.com/speakin/sdk_go"
"github.com/speakin/sdk_go/openapi"
"github.com/antihax/optional"
"os"
"time"
)
func uploadFile(filename string, client *openapi.APIClient, bucket string) string {
voice, err := os.Open(filename)
if err != nil {
panic(err)
}
resp, _, err := client.StorageApi.Upload(context.Background(),
bucket, "wav", 0, time.Now().Unix(),
&openapi.UploadOpts{Body: optional.NewInterface(voice)})
if err != nil {
panic(err)
}
if resp.HasError {
fmt.Printf("%s:%s", resp.ErrorId, resp.ErrorDesc)
os.Exit(1)
}
return resp.Data.Key
}
func main() {
// 修改为你的app key和bucket key
client := sdk.NewClient(
"your_access_key", // app access key
"your_secret_key", // app secret key
"your_bucket_access_key", // bucket access key
"your_bucket_secret_key", // bucket secret key
)
// 修改为你的bucket名字和app名字
bucket := "bucket-test"
appName := "app-test"
// voice file
userNames := []string{"user_a", "user_b"}
userFiles := [][]string{
{
"/testdata/audio-data/u1/01_16k.wav",
"/testdata/audio-data/u1/02_16k.wav",
"/testdata/audio-data/u1/03_16k.wav",
},
{
"/testdata/audio-data/u2/01_16k.wav",
"/testdata/audio-data/u2/02_16k.wav",
"/testdata/audio-data/u2/03_16k.wav",
},
}
userFilesKey := make([][]string, 2)
// 上传所有注册文件
for i := range userNames {
for _, filename := range userFiles[i] {
userFilesKey[i] = append(userFilesKey[i], uploadFile(filename, client, bucket))
}
}
// 注册
for i, name := range userNames {
req := openapi.VoiceprintRegisterRequest{
AppName: appName,
UnionID: name,
Urls: userFilesKey[i],
SamplingRate: "16k",
Timestamp: time.Now().Unix(),
Replace: true,
}
resp, _, err := client.VoiceprintApi.Register(context.Background(),
&openapi.RegisterOpts{VoiceprintRegisterRequest: optional.NewInterface(req)})
if err != nil {
panic(err)
}
if resp.HasError {
fmt.Printf("%s:%s\n", resp.ErrorId, resp.ErrorDesc)
os.Exit(1)
}
}
// 校验注册结果
for _, name := range userNames {
req := openapi.VoiceprintQueryRequest{
AppName: appName,
UnionID: name,
Timestamp: time.Now().Unix(),
}
resp, _, err := client.VoiceprintApi.Query(context.Background(),
&openapi.QueryOpts{VoiceprintQueryRequest: optional.NewInterface(req)})
if err != nil {
panic(err)
}
if resp.HasError {
fmt.Printf("%s:%s\n", resp.ErrorId, resp.ErrorDesc)
os.Exit(1)
}
fmt.Printf("user %s register: %v\n", name, resp.Data.IsRegister)
}
// 1比1比对
checkFiles := []string{
"/testdata/audio-data/u1/04_16k.wav",
"/testdata/audio-data/u2/04_16k.wav",
}
// 上传upload文件
checkFilesKeys := []string{
uploadFile(checkFiles[0], client, bucket),
uploadFile(checkFiles[1], client, bucket),
}
// 1比1验证文件
for _, name := range userNames {
for i, key := range checkFilesKeys {
req := openapi.VoiceprintVerifyRequest{
AppName: appName,
UnionID: name,
Url: key,
SamplingRate: "16k",
Timestamp: time.Now().Unix(),
}
resp, _, err := client.VoiceprintApi.Verify(context.Background(),
&openapi.VerifyOpts{VoiceprintVerifyRequest: optional.NewInterface(req)})
if err != nil {
panic(err)
}
if resp.HasError {
fmt.Printf("%s:%s\n", resp.ErrorId, resp.ErrorDesc)
os.Exit(1)
}
fmt.Printf("user %s verify file %s score: %v\n", name, checkFiles[i], resp.Data.Score)
}
}
// 1比n验证
for i, key := range checkFilesKeys {
req := openapi.Voiceprint1tonVerifyRequest{
AppName: appName,
UnionIDs: userNames,
Url: key,
SamplingRate: "16k",
Timestamp: time.Now().Unix(),
}
resp, _, err := client.VoiceprintApi.Verify1ton(context.Background(),
&openapi.Verify1tonOpts{Voiceprint1tonVerifyRequest: optional.NewInterface(req)})
if err != nil {
panic(err)
}
if resp.HasError {
fmt.Printf("%s:%s\n", resp.ErrorId, resp.ErrorDesc)
os.Exit(1)
}
fmt.Printf("verify file %s match user %s score: %v\n", checkFiles[i], resp.Data.UnionID, resp.Data.Score)
}
// 下载 TODO openapi有bug暂时无法使用
{
//_, resp, err := client.StorageApi.Download(context.Background(),
// bucket, checkFilesKeys[0])
//if err != nil {
// panic(err)
//}
//
//b, err := ioutil.ReadAll(resp.Body)
//if err != nil {
// panic(err)
//}
//ioutil.WriteFile("/testdata/tmp/tmp.wav", b, 0644)
}
}
git clone https://github.com/speakin/sdk_java
mvn install
// 加载api客户端 替换你的key
SecApiClient apiClient = new SecApiClient(
"your app access key", // app access key
"your app secret key", // app secret key
"your bucket access key",// bucket access key
"your bucket secret key" // bucket secret key
);
package mobi.speakin.sdk.example;
import mobi.speakin.sdk.voiceprint_cloud.SecApiClient;
import mobi.speakin.sdk.voiceprint_cloud.SecStorageApi;
import mobi.speakin.sdk.voiceprint_cloud.SecVoiceprintApi;
import mobi.speakin.sdk.voiceprint_cloud.gen.model.*;
import static org.junit.Assert.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
public class Example {
private static String uploadFile(String fileName, SecStorageApi storageApi, String bucket) throws Exception {
String filePath = Example.class.getClassLoader().getResource(fileName).getFile();
File file = new File(filePath);
CallUploadResponse resp = storageApi.upload(bucket, "wav", 0L, System.currentTimeMillis(), file);
if(resp.getHasError()){
throw new RuntimeException(resp.getErrorId());
}
return resp.getData().getKey();
}
public static void main(String[] args) throws Exception {
// 加载api客户端 替换你的key
SecApiClient apiClient = new SecApiClient(
"your app access key", // app access key
"your app secret key", // app secret key
"your bucket access key",// bucket access key
"your bucket secret key" // bucket secret key
);
SecStorageApi storageApi = new SecStorageApi(apiClient);
SecVoiceprintApi api = new SecVoiceprintApi(apiClient);
String[] userNames = {"customer_1", "customer_2"};
// 替换成你的app name和bucket name
String appName = "app-test";
String bucketName = "bucket-test";
// 上传所有注册音频文件,并把文件key储起来
String[][] usersFiles = {
{
"./audio-data/u1/01_16k.wav",
"./audio-data/u1/02_16k.wav",
"./audio-data/u1/03_16k.wav",
},
{
"./audio-data/u2/01_16k.wav",
"./audio-data/u2/02_16k.wav",
"./audio-data/u2/03_16k.wav",
},
};
ArrayList > usersFilesKey = new ArrayList<>();
for (String[] files : usersFiles){
ArrayList keys = new ArrayList<>();
for (String fileName: files){
keys.add(uploadFile(fileName, storageApi, bucketName));
}
usersFilesKey.add(keys);
}
// 注册所有用户
for(int i = 0;i < userNames.length;i++) {
VoiceprintRegisterRequest registerReq = new VoiceprintRegisterRequest();
registerReq.setAppName(appName);
registerReq.setUrls(usersFilesKey.get(i));
registerReq.setUnionID(userNames[i]);
registerReq.setTimestamp(System.currentTimeMillis());
registerReq.setSamplingRate("16k");
registerReq.setReplace(true);
RespVoiceprintRegisterResponse registerRsp = api.register(registerReq);
if(registerRsp.getHasError()){
throw new RuntimeException(registerRsp.getErrorId());
}
}
// 查询用户是否存在
for (String userName : userNames) {
VoiceprintQueryRequest queryReq = new VoiceprintQueryRequest();
queryReq.setAppName(appName);
queryReq.setUnionID(userName);
queryReq.setTimestamp(System.currentTimeMillis());
RespVoiceprintQueryResponse queryRsp = api.query(queryReq);
if (queryRsp.getHasError()) {
throw new RuntimeException(queryRsp.getErrorId());
}
System.out.printf("user %s register: %s\n", userName, queryRsp.getData().getIsRegister());
}
// 验证用户音频
String[] checkFiles = {
"./audio-data/u1/04_16k.wav",
"./audio-data/u2/04_16k.wav",
};
String[] checkFilesKeys = {
uploadFile(checkFiles[0], storageApi, bucketName),
uploadFile(checkFiles[1], storageApi, bucketName),
};
for(String name : userNames) {
for(int i = 0;i < checkFilesKeys.length;i++) {
VoiceprintVerifyRequest verifyReq = new VoiceprintVerifyRequest();
verifyReq.setAppName(appName);
verifyReq.setUnionID(name);
verifyReq.setTimestamp(System.currentTimeMillis());
verifyReq.setUrl(checkFilesKeys[i]);
verifyReq.setSamplingRate("16k");
RespVoiceprintVerifyResponse verifyRsp = api.verify(verifyReq);
if(verifyRsp.getHasError()){
throw new RuntimeException(verifyRsp.getErrorId());
}
System.out.printf("user %s verify file %s score %s\n", name, checkFiles[i], verifyRsp.getData().getScore());
}
}
// 1比n 验证
for(int i = 0;i < checkFilesKeys.length;i++) {
Voiceprint1tonVerifyRequest verifyMultipleReq = new Voiceprint1tonVerifyRequest();
verifyMultipleReq.setAppName(appName);
verifyMultipleReq.setUnionIDs(Arrays.asList(userNames));
verifyMultipleReq.setTimestamp(System.currentTimeMillis());
verifyMultipleReq.setUrl(checkFilesKeys[i]);
verifyMultipleReq.setSamplingRate("16k");
RespVoiceprint1tonVerifyResponse verifyMultipleRsp = api.verify1ton(verifyMultipleReq);
if(verifyMultipleRsp.getHasError()){
throw new RuntimeException(verifyMultipleRsp.getErrorId());
}
System.out.printf("verify file %s match user %s score: %s\n", checkFiles[i], verifyMultipleRsp.getData().getUnionID(), verifyMultipleRsp.getData().getScore());
}
// 下载上传的音频文件
{
File file = storageApi.download(bucketName, checkFilesKeys[0]);
System.out.printf("file at %s",file.toURL().toString());
}
}
}
错误ID | 错误说明 |
---|---|
common.miss_param | 缺少请求参数 |
common.wrong_time_stamp | 错误的时间戳,请求时间和服务器时间相差太大 |
common.wrong_sign | 错误的数据签名 |
common.wrong_data | 错误的数据 |
common.unkwon | 未知错误 |
common.unkwon_app_id | 未知的app id |
common.unkwon_session_id | 未知的session id |
common.invalid_id_type | 非法的id类型 |
user.wrong_type | 错误的用户类型 |
user.not_exist | 用户不存在 |
user.no_parent | 父用户不存在 |
user.no_child | 子用户不存在 |
user.not_valid | 用户被禁了 |
record.pre_not_done | 上一个上传流还未结束 |
record.wrong_id | 错误的上传流ID |
record.wrong_target | 错误的语音用途 |
record.target_not_allow | 语音用途不被允许 |
record.no_module | 没有处理该语音的算法模块 |
record.not_start | 上传流还未开始 |
record.unsupport_data_format | 不支持的语音格式 |
record.snr_too_low | 语音信噪比过低 |
record.speech_too_short | 语音时间太短 |
record.volumn_too_low | 语音声音太小 |
record.wrong_data | 错误的语音数据 |
record.wrong_voice_bit_count | 错误的语音bit数 |
record.wrong_voice_rate | 错误的采样率 |
register.wrong_record_id | 错误的语音ID |
register.need_more_record | 需要更多语音完成注册 |
register.multi_module | 多个算法模型冲突 |
register.no_module_found | 没有可用的算法模块 |
verify.wrong_record_id | 错误的语音ID |
verify.no_module_found | 没有可用的算法模块 |
verify.need_register | 缺少声纹,需要先注册 |
identity.wrong_record_id | 错误的语音ID |
identity.no_module_found | 没有可用的算法模块 |
Q:离线版SDK支持
A:目前声纹云服务连接公有云访问,也可为合作商搭建自己的私有云声纹库,例如金融、银行、公安系统等。离线版SDK正在性能测试中,即将上线。
Q:App ID的使用规范
A:对于每个使用我们声纹云接口的用户,我们都会提供一个app
id和app secret,需确保 App ID与secret匹配。
不同平台(Android/iOS/PC等),但是作为同一应用,需确保App
ID一致。
Q:上传音频的采样率与采样精度
A:采样率16KHZ或者8KHZ(根据所选用的模型不同有所区分),单声道,采样精度16bit的PCM或者WAV格式的音频。详情请参考语音标准。
Q:英文识别
A:目前已提供中文识别的4种模型,英文识别正在性能测试中,即将上线。
Q:录入语音时会话时长是多久
A:根据不同的模型,注册声纹与验证声纹均有不同的时长要求,请参考语音标准。
过短的录音不满足语音标准将会返回错误提示;过长的语音将由服务端处理提取有效语音,建议根据实际使用场景制定录入时长。
Q:不同声纹模型之间的语音是否通用
A:注册声纹与验证声纹的模型需一致,例如数字模型注册的声纹只能通过数字模型来验证。但是用户实际录入的语音内容无法控制,因此需在产品层面引导
Q:数据是否要加wav头?是否可以直接mic数据至服务器?
A:wav文件由一个文件头及pcm数据组成,文件头是用于标识该wav文件的pcm数据采样率、量化比特数、文件长度等信息。
mic录音得到的就是pcm数据,可以直接上传语音云进行识别,不需要添加文件头。
需要注意,mic录音的pcm数据采样率、量化比特数、声道数需符合录入标准参数
Q:Java开发,jre配置与jce版本
A:由于jre默认配置对加密强度有限制,需要更新对应版本的jce。
Q:centos、ubuntu安装依赖
A:
centos
sudo yum install libcurl-devel
sudo yum install openssl-devel
sudo apt-get install libcurl4-openssl-dev
sudo apt-get install openssl
sudo apt-get install libssl-dev