开发平台

development platform

声纹识别可用于什么场景

声纹识别是一种经济、可靠、简便和安全的身份识别方式,语音波形反映说话人生理和行为特征的语音参数,自动识别说话人身份的技术,在安全性上可与其他生物识别技术(指纹、掌形和虹膜)相媲美,且只需麦克风即可,数据采集极为方便,造价低廉。

声纹识别在公共安全监控,刑事侦查音谱分析,司法声纹证据,银行、互联网金融贷款身份/黑名单识别,社会保险发放身份确认,移动支付身份验证,智能硬件用户识别个性化服务等均可应用

声纹使用要点

声纹识别服务要获得期望的效果,须正确录入语音。

声纹识别的操作可分为两种:注册声纹(声音特征建模)和声纹对比。有效的声纹必须准确、纯粹、完全地反映说话人的声音特种,因此声纹录入时必须做到以下几点:


1. 说话人的有效语音时长足够,为了能让机器完全掌握声音特征,对于不同的模型,录音的有效时长有着不同的标准,详情请查看语音标准


2. 说话人语音需保持纯粹,即录入时需避免混入其他人的声音或过大的环境噪音(如喇叭声、马达声等),虽然Speakin在算法上进行了优化处理与提取,但是更加纯粹的语音录入,有助于声纹识别的准确性。


3. 说话人的讲话方式需保持正常,不可刻意进行变声、拖音、语速过度变化、声音强度过度变化等,这些行为均为导致声纹特征的改变,不利于声纹识别的准确性。

如何使用声纹云服务

1. SpeakIn声纹云开放平台提供了不同语言版本的SDK下载,根据用户需求集成SDK后即可使用声纹云服务。


2. 为了正常使用SDK,必须在声纹云平台注册账号并认证为开发者,通过开发者账号创建应用申请APP ID使用。


3. SpeakIn提供公有云服务,需连接网络,如需搭建私有云,请与我们联系。

AppID有什么作用

1. 声纹云App ID 是SDK声纹识别服务的必要秘钥。


2. 声纹云App ID 影响着声纹模型的使用,根据不同场景(语音时常,环境噪音,数据或自由文本,采样率等)及用户需求,每个App ID将被分配一个或多个算法模型。 ID使用。


什么是声纹模型

声纹模型指不同的语音密码类型。


根据不同场景(语音时常,环境噪音,数字或自由文本,采样率等)及用户需求,目前SpeakIn声纹云提供4类模型:数字模型(数字为随机数字串)、短自由文本模型(任意中文短语)、短固定文本模型(固定的中文短语)、长自由文本模型(任意中文长语音)。


注册与验证的声纹模型需一致,才能进行正常识别。

如何通过声纹进行识别验证

1. 设置声纹用户,通过麦克风录入语音,进行声纹注册。


2. 1:1声纹验证,声纹注册完成后,再录入新语音,即可使用新语音对原注册语音进行对比,得出声纹是否一致的结果。


3. 1:N声纹识别,声纹注册完成后,再录入新语音,即可使用新语音对复数的语音进行对比,找出匹配的声纹。


4. 录入语音需满足一定标准才可得到高识别率,具体系数请参考语音标准


5. 需确保注册与验证的声纹模型一致,例如注册为数字模型,验证时需选择数字模型验证。

快速开始


使用声纹云服务前必须要有:


声纹云系统
  • app name: 声纹云系统用户名
  • app access key: 声纹云访问密钥
  • app secret key: 声纹云加密密钥
声纹云存储
  • bucket name: 声纹云存储用户名
  • bucket access key 存储访问密钥
  • bucket secret key 存储加密密钥

声纹云系统用作比较/查询/注册等一些业务请求


声纹云存储则用来上传/下载文件


app和bucket都可以通过bd@speakin.mobi邮箱申请

使用流程


1. 上传文件流程

上传文件后,声纹云会返回一个文件key作为标识,当注册/验证时使用这个key标识来代表文件




2. 注册用户流程

使用音频文件来注册用户,我们会把音频文件的特征和用户关联




3. 校验用户流程

校验用户的声纹特征值




4. 1比1完整验证流程

服务端上传声纹文件验证流程




客户端上传声纹文件验证流程




5. 1比N完整验证流程

服务端上传声纹文件验证流程




客户端上传声纹文件验证流程


GO SDK


安装

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)
    }

}
                        
                    

JAVA SDK


安装


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());
        }

    }
}
                    

语音标准


1. 数字模型



2. 短自由文本模型



3. 短固定文本模型



4. 长自由文本模型

错误代码


错误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


ubuntu

sudo apt-get install libcurl4-openssl-dev

sudo apt-get install openssl

sudo apt-get install libssl-dev