1. 概述

声纹识别技术(Voiceprint Recognition),简称VPR,是生物识别技术的一种,提取说话人的语音身份特征,提供声纹鉴别和声纹确认,也称为说话人识别,有两类,即说话人辨认和说话人确认。不同的任务和应用会使用不同的声纹识别技术,如缩小刑侦范围时可能需要辨认技术,而银行交易时则需要确认技术。声纹识别就是把声信号转换成电信号,再用计算机进行识别。

本文档旨在讲解如何快速地集成灵云VPR功能到开发者应用中。关于各服务接口更详细的说明,请参考灵云SDK开发手册。在集成过程中如有疑问,可登录灵云开发者论坛,查找答案或与其他开发者交流。

1.1 概念解释

对指南中涉及的名词作简要描述,使开发者在阅读开发指南时,对此有一些大致的了解和预备知识的了解。

名称 说明
Session Session用来标记一个能力运行过程的上下文。一个应用最多可同时创建的Session数受到授权的限制
云端识别 由云端服务器提供识别结果的方式。使用云端能力需要加上hci_vpr_cloud_recog模块
特征训练(注册) 为提高音频的识别正确率,灵云VPR提供特征训练的方法,通过大量音频的注册训练,可以提高识别的速度和正确率。详见hci_vpr_enroll函数
特征确认 声纹模型注册和训练成功后,可以对音频数据进行校验,以判断用户唯一标识和音频数据是否相符。详见hci_vpr_verify函数
特征辨识 对音频数据校验后,需要对音频数据识别,识别结果以分数形式返回。详见hci_vpr_identity函数

1.2 能力定义(capkey)

VPR 支持多种能力,通过 capkey 区分不同能力的调用和配置。其中常用 capkey 如下所示:

capkey 支持音频格式
vpr.cloud.recog alaw8k8bit
ulaw8k8bit
pcm8k16bit
alaw16k8bit
ulaw16k8bit
pcm16k16bit
vox6k4bit

其中本地声纹识别资源列表: |资源目录|支持音频格式| |------|-----------| |16k|pcm16k16bit
ulaw16k8bit
alaw16k8bit| |8k|pcm8k16bit
ulaw8k8bit
alaw8k8bit|

1.3 识别流程

2. 能力使用说明

2.1 准备工作

下载声纹识别SDK并解压缩。

2.2 使用iOS版SDK

  • 必选模块

    • libcurl_device_simulator_iOS7.1.a
    • libhci_sys_device_simulator_iOS7.1.a
    • libhci_vpr_device_simulator_iOS7.1.a
  • 云端识别

    • libhci_vpr_cloud_recog_device_simulator_iOS7.1.a
    • libjtspeex_device_simulator_iOS7.1.a
    • libjtopus_device_simulator_iOS7.1.a
  • 适配器选择
    • libhci_vpr_only_cloud_adapter_device_simulator_iOS7.1.a(只使用云端能力) <!-- - libhci_vpr_only_local_adapter_device_simulator_iOS7.1.a(只使用本地能力)
    • libhci_vpr_cloud_local_adapter_device_simulator_iOS7.1.a(既使用云端能力,又使用本地能力) -->

PS:

在集成灵云的iOS版本SDK时,静态库目录中会提供一种名称中包含adapter的静态库文件。值得说明一点的是,包含adapter名称的静态库文件是作为适配器使用的,所谓适配器是指开发者使用的灵云能力,即capkey是云端能力、本地能力,亦或者是云+端能力,所以适配器文件也还会对应以上的能力范围。当开发者集成时,适配器静态库在集成到工程中,有且只能有一个适配器静态库被添加到Link目录中,如开发者使用ASR的本地自由说能力,则适配器只需要选择libhci_asr_only_local_adapter_device_simulator_iOS7.1.1.a即可。

3. 能力集成说明

在调用VPR能力之前,需要初始化灵云SDK的SYS通用模块。

3.1 通用模块初始化

  • Android示例代码

      // 创建初始化参数辅助类
      InitParam initparam = new InitParam();
      // 授权文件所在路径,此项必填
      String authDirPath = context.getFilesDir()  .getAbsolutePath();;
      initparam.addParam  (InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authDirPath)    ;
      // 灵云云服务的接口地址,此项必填
      initparam.addParam  (InitParam.AuthParam.PARAM_KEY_CLOUD_URL,     "http://api.hcicloud.com:8888");
      // 开发者密钥,此项必填,由捷通华声提供
      initparam.addParam  (InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY,     "01234567890");
      // 应用程序序号,此项必填,由捷通华声提供
      initparam.addParam  (InitParam.AuthParam.PARAM_KEY_APP_KEY, "1234abcd");
      // 日志的路径,可选,如果不传或者为空则不生成日志
      String logDirPath =     "/storage/emulated/0/sinovoice/com.sinovoice.   example/log";
      initparam.addParam  (InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH,  logDirPath);
      //日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试
      //SDK将输出小于等于logLevel的日志信息
      initparam.addParam  (InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");
      // 灵云系统初始化
      // 第二个参数在Android平台下,必须为当前的Context
      int errCode = HciCloudSys.hciInit   (initparam.getStringConfig(), this);
      if(errCode != HciErrorCode.HCI_ERR_NONE) {
          // "系统初始化失败"
          return;
      }
    

3.2 授权检测

在初始化灵云SDK的通用模块后,还需要调用授权检测函数获取云端授权。

  • Android示例代码

      // 获取授权
      private int checkAuthAndUpdateAuth() {
          // 获取系统授权到期时间
          int initResult;
          AuthExpireTime objExpireTime = new AuthExpireTime   ();
          initResult = HciCloudSys.hciGetAuthExpireTime   (objExpireTime);
          if (initResult == HciErrorCode.HCI_ERR_NONE) {
              // 显示授权日期,如用户不需要关注该值,此处代码可忽   略
              Date date = new Date    (objExpireTime.getExpireTime() * 1000);
              SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd",Locale.CHINA);
              Log.i(TAG, "expire time: " + sdf.format(date))  ;
    
              if (objExpireTime.getExpireTime() * 1000 >  System.currentTimeMillis()) {
                  Log.i(TAG, "checkAuth success");
                  return initResult;
              }
          }
    
          // 获取过期时间失败或者已经过期
          initResult = HciCloudSys.hciCheckAuth();
          if (initResult == HciErrorCode.HCI_ERR_NONE) {
              Log.i(TAG, "checkAuth success");
              return initResult;
          } else {
              Log.e(TAG, "checkAuth failed: " + initResult);
              return initResult;
          }
      }
    

3.3 VPR初始化

  • iOS示例代码
//本地能力所需本地资源所在路径
NSString *dataPath = [[NSBundle mainBundle] pathForResource:@"data" ofType:Nil];  
//initCapKeys为必填项。其他可配置信息请查询开发手册hci_vpr_init函数说明。
NSString *initCapKeys = @"vpr.local.recog";
NSString *config = [NSString stringWithFormat:@"initCapKeys=%@,dataPath=%@",
                initCapKeys,dataPath];
HCI_ERR_CODE errCode = HCI_ERR_NONE;
errCode = hci_vpr_init(config.UTF8String);
if (errCode != HCI_ERR_NONE){
    NSLog(@"hci_vpr_init failed, error:%@", errCode);
    eturn NO;
}
NSLog(@"hci_vpr_init success");
return YES;

3.4 开启会话(session)

VPR 是通过会话(session)来管理识别过程的。在VPR能力初始化成功之后,需要通过开启识别会话来完成识别。

  • iOS示例代码
// 开启session
HCI_ERR_CODE errCode = HCI_ERR_NONE;
NSString *config = @"capKey=vpr.local.recog";
int sessionID;            // 开启会话成功后,会返回的会话ID
errCode = hci_vpr_session_start(config.UTF8String, &sessionID);
if (errCode != HCI_ERR_NONE){
    NSLog(@"hci_vpr_session_start failed, error:%@", errCode);
    return NO;
}
NSLog(@"hci_vpr_session_start success");
return YES;

3.5 声纹特征训练或注册

在启动会话(session)成功后,即可以进行VPR的声纹特征训练。

  • iOS示例代码
//VPR声纹特征训练
//添加训练数据
int enrollDataCount = 0;
VPR_ENROLL_VOICE_DATA_ITEM enrollItemArray[4];

NSString *tmpDataPath = [NSString stringWithFormat:@"%@/tmp",
    NSTemporaryDirectory()];

NSFileManager *fileMgr = [NSFileManager defaultManager];
for (int iTmp = 0;iTmp < ENROLL_DATA_COUNT;iTmp++){
    NSString *path = [[NSBundle mainBundle] pathForResource:
                    [NSString stringWithFormat:@"enroll_%d",iTmp] ofType:@"pcm"];

    NSFileHandle *readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
    NSDictionary *attr =[fileMgr attributesOfItemAtPath:path error:nil];
    NSUInteger fileSize = attr.fileSize;

    NSData *data = [readFileHandle readDataOfLength:fileSize];

    enrollItemArray[enrollDataCount].pvVoiceData = (void *)data.bytes;
    enrollItemArray[enrollDataCount].uiVoiceLength = (unsigned int)data.length;
    enrollDataCount ++;
}

VPR_ENROLL_VOICE_DATA enrollData;
enrollData.uiVoiceDataCount = enrollDataCount;  //训练音频的数量
enrollData.psVoiceDataList = enrollItemArray;   //待训练音频数组

NSString *userId = @"12313";            //用户唯一标识符
NSString *enrollConfig = [NSString stringWithFormat:@"userid=%@",userId];

err_code = hci_vpr_enroll(sessionID,&enrollData,enrollConfig.UTF8String,
    &enrollResult);
if (err_code != HCI_ERR_NONE){
    NSLog(@"hci_vpr_enroll return (%d)",err_code);
}
else{
    NSLog(@"hci_vpr_enroll success");

    //返回训练结果的userid    
    NSLog(@"enroll result is userid=%@",
        [NSString stringWithFormat:@"%s",enrollResult.pszUserId]);
    hci_vpr_free_enroll_result(&enrollResult);
}

3.6 声纹特征确认

  • Android示例代码
  • iOS示例代码
//VPR声纹特征确认
//待确认音频文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"verify" ofType:@"pcm"];
NSData *voiceData = [NSData dataWithContentsOfFile:path];

NSString *userId = @"12313";            //用户唯一标识符
NSString *verifyConfig = [NSString stringWithFormat:@"userid=%@",userId];

NSLog(@"hci_vpr_verify config [%@]",verifyConfig);

err_code = hci_vpr_verify(sessionID, (void *)voiceData.bytes, 
            (unsigned int)voiceData.length,
         verifyConfig.UTF8String, &verifyResult);
if(err_code != HCI_ERR_NONE){
    NSLog(@"hci_vpr_verify return (%d)",err_code);
}else{
    NSLog(@"hci_vpr_verify success");
    NSLog(@"the result score is :%d",verifyResult.uiScore);

    if (verifyResult.eStatus == VPR_VERIFY_STATUS_MATCH){
        NSLog(@"voice data matches with user id:%@!",_userId);
    }
    else{
        NSLog(@"voice data doesn't match with user id:%@ !",_userId);
    }
    hci_vpr_free_verify_result(&verifyResult);
}

3.7 声纹辨识确认

  • iOS示例代码
//待辨识音频文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"verify" ofType:@"pcm"];
NSData *voiceData = [NSData dataWithContentsOfFile:path];

NSString *groupId = @"xxxxxxxxx";            //用户组id
NSString *identifyConfig = [NSString stringWithFormat:@"groupid=%@",groupId];
NSLog(@"hci_vpr_identify config [%@]",identifyConfig);

//辨识确认
NSData *voiceData = [NSData dataWithContentsOfFile:path];

err_code = hci_vpr_identify(sessionID, (void *)voiceData.bytes, 
            (unsigned int)voiceData.length, identifyConfig.UTF8String, 
                &identifyResult);
if( err_code != HCI_ERR_NONE )
{
    NSLog(@"hci_vpr_identify return (%d)",err_code);
    hci_vpr_session_stop(_sessionID);
    return;
}

NSLog(@"hci_vpr_identify success");
[self printIdentifyResult:identifyResult];
for (int index = 0; index < (int)result.uiIdentifyResultItemCount; ++index){
    VPR_IDENTIFY_RESULT_ITEM& item = result.pIdentifyResultItemList[index];
    NSLog(@"index:%d", index);
    NSLog(@"userid:%s",item.pszUserId);
    NSLog(@"score:%d",item.uiScore);
}
hci_vpr_free_identify_result(&identifyResult);

3.8 结束识别

最后我们需要反初始化,依次关闭会话,终止AFR能力,关闭灵云系统。

  • iOS示例代码
//关闭VPR识别会话
errCode = hci_vpr_session_stop(nSessionId);
//终止VPR能力
errCode = hci_vpr_release();
//终止灵云系统
errCode = hci_release();

4. 常用配置说明

VPR支持实时识别模式,即realtime=yes。可以在hci_vpr_session_start传入该参数,启动实时识别session。 以声纹辨识为例:

  • iOS示例代码

NSString *path = [[NSBundle mainBundle] pathForResource:@"verify" ofType:@"pcm"];
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSDictionary *attr = [fileMgr attributesOfItemAtPath:path error:nil];

NSUInteger fileSize = attr.fileSize;

NSUInteger offset = 3200 * 20;           //送入训练的每片音频大小
//片数
NSUInteger chunks = (fileSize%offset==0)?(fileSize/offset):(fileSize/(offset)+1); 
NSFileHandle *readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path];

for (int i = 0; i < chunks; ++i) {
    NSUInteger currentSize;
    if (i != chunks - 1) {
        currentSize = offset;
    } else {
        currentSize = fileSize - i * offset;
    }
    [readFileHandle seekToFileOffset:i * offset];
    NSData *subData = [readFileHandle readDataOfLength:currentSize];
    err_code = hci_vpr_identify(sessionID, (void *)subData.bytes, 
                (unsigned int)subData.length, identifyConfig.UTF8String,
                 &identifyResult);

    if (err_code == HCI_ERR_PARAM_INVALID){
        NSLog(@"hci_vpr_identify return (%d)",err_code);
        break;
    }
    if (err_code == HCI_ERR_NONE){
        break;
    }

}

//在实时模式场景,忽略HCI_ERR_VPR_REALTIME_WAITING的情况,继续注册后面的音频。
//HCI_ERR_VPR_REALTIME_WAITING (实时识别等待音频)含义是:还没有数据,或者是需要更多数据。
if( err_code == HCI_ERR_VPR_REALTIME_WAITING || err_code == HCI_ERR_NONE )
{
    err_code = hci_vpr_identify(sessionID, NULL, 1, 
        identifyConfig.UTF8String, &identifyResult);
    if (err_code != HCI_ERR_NONE)
    {
        NSLog(@"hci_vpr_identify return (%d)",err_code);
    }
}
if( err_code == HCI_ERR_NONE )
{
    //打印辨识结果
    [self printIdentifyResult:identifyResult];

    //释放辨识结果
    hci_vpr_free_identify_result(&identifyResult);
    if( err_code != HCI_ERR_NONE )
    {
        NSLog(@"hci_vpr_free_recog_result return %d", err_code);
        hci_vpr_session_stop(_sessionID);
        return ;
    }
    NSLog(@"hci_vpr_free_recog_result success");
}
else
{
    NSLog(@"hci_vpr_recog failed with %d", err_code);
}

5. FAQ

Q: 我运行你们的 SDK 报 19 号错误:START LOG FAILED, 应该如何解决? A: 出现此问题,一般由两种原因造成。

工程文件里引入了中文路径,导致SDK启动时,读写对应的日志路径报错。 日志文件定义的路径由于权限问题不可读写,比如在 Android 平台上运行时,没有 WRITE_EXTERNAL_STORAGE的权限。

Q: 我运行你们的 SDK 报 23 号错误:LOAD_FUNCTION_FROM_DLL FAILED,应该如何解决? A: 这种情况,通常是由于缺库。最常见的场景是使用本地能力的 capkey,如 vpr.loacl.recog,没有将库文件对应组入(我们 SDK 示例程序,与 example 同层有全套库文件,示例程序内默认只组入了云端库),将相关库补齐即可使用。此外,由于 VPR能力引用了第三方的 OPENCV, MKL 库,因此如果第三方库没有拷入,也会导致此问题。

Q: 我运行你们的 SDK 报 14 号错误:LOCAL_RES_MISSING,应该如何解决? A: 本地能力常会遇到此问题,此问题是由于没有组入对应的本地资源文件。请在我们开发者社区下载需求的本地资源(VPR本地模型)等,并将资源文件组入代码 datapath 指定路径,然后再次尝试运行程序。

Q: 我运行你们的 SDK 报 11 号错误:SERVICE_RESPONSE_FAILED ,应该如何解决? A: 此问题原因较为复杂,可能是传入的参数问题与服务器不匹配,如调用私有云时,没有传入 property 等配置项。也可能是服务端内部的返回原因,请联系技术支持排查。

Q: 我运行你们的SDK报 7 号错误:CONFIG_UNSUPPORT ,应该如何解决? A: 与 11号错误类似,还是配置串问题,7 号错误原因为传入了不支持的配置串。请仔细检查各接口传入的所有配置串,并联系技术支持排查。

Q: 我运行你们的SDK报 9 号错误: SERVICE_TIMEOUT ,应该如何解决? A: 这个是因为您请求我们服务器到返回结果的时间,超过了上限。请检查网络环境相关的原因,并协助提供日志。

Q: 我运行你们的 SDK 报 16 号错误: SESSION_INVALID ,应该如何解决? A: 这个需要检查详细的代码调用过程,调用每个能力核心接口时,都是需要传入 sessionid 的,如果在 sessionstart 的时候,已经发生了错误,或者使用的id,并不是 sessionstart 时候创建的,就会返回此错误,请联系技术支持排查。

Q: 我运行你们的 SDK 报 0 号错误: ERROR NONE,应该如何解决? A: SDK 报 0 号错误, ERROR NONE,代表执行成功,请您继续后续的集成。

results matching ""

    No results matching ""