iOS语音识别功能实现

现在市面上的即时通讯app都有语音转文本的功能, 那么语音转文本如何实现呢? 自己写是不现实的, 因为这涉及到模式识别的算法, 所以只能用到第三方SDK了, 我在项目中用到的是百度语音SDK, 自己也是刚刚实现功能, 很多地方都还需要优化, 就简单介绍一下如何实现吧

一 注册应用
  • 首先登录百度语音
    登录百度语音
  • 在应用管理中添加新的应用


    创建新应用
  • 创建到第4步时, 把bundle identifier填进去


    填入应用包名
  • 创建成功后, 可以拿到App ID, API Key ,Secret Key, 这些东西都是后面配置用得到的
查看Key
二 配置环境
  • 下载iOS版本的SDK


    下载iOS版本的SDK
  • 下载了官方的SDK之后, 可以看到以下文件

官方SDK文件目录
  • 如果嫌麻烦的话, 直接把除了Doc和Sample文件的其余文件全部拖到工程里去
  • 添加依赖库
添加依赖库1

添加依赖库2

关于添加依赖库, 百度官方是这样解释的:

BDVRClient 使用了录音和播放功能,因此需要在 Xcode 工程中引入 AudioToolbox.framework和 AVFoundation.framework;BDVRClient 还使用到了网络状态检测功能,因此还需要引入SystemConfiguration.framework;为了生成设备 UDID,需要引入 Security.framework;为了支持 gzip压缩,需要引入 libz.1.dylib; 网络模块需要引入 CFNetwork.framework;某些场景需要获取设备地理位置以 高识别准确度,需引入 CoreLocation.framework。为了支持识别控件,需要引入 OpenGLES.framework,QuartzCore.framework,GLKit.framework,CoreGraphics.framework 和 CoreText.framework。

添加方式:右键点击 Xcode 中的工程文件,在出现的界面中,选中 TARGETS 中应用,在出现的界面中选中 Build Phase->Link Binary With Libraries,点击界面中的“+”图标,在弹出的界面中选择此 7 个 Framework 即可,添加完成效果图如图 8 所示(libBDVoiceRecognitionClient.a 将在随后添加

  • 添加完成后先预编译一下, 我在编译的时候遇到的问题都是由于libBDVoiceRecognitionClient.a这个静态库没有正确添加导致的

  • 另外一个问题是我现在的工程文件只能在真机环境下运行, 在模拟器的环境运行始终报错, 目前我还没找到合适的解决方法

三 开始写代码
  • 首先创建一个音频识别管理类
创建音频识别管理类

**Note: 需要注意的是, 静态库采用Objective C++实现, 因此, 需要保证工程中引用静态库头文件的实现文件的扩展名必须为.mm **

  • 我们实现一个单例构造方法
+ (instancetype)sharedRecognizer {
   static ZHLVoiceRecognizer *recognizer = nil;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       recognizer = [[ZHLVoiceRecognizer alloc] init];
   });
   return recognizer;
}
  • 在这个类中,我们添加一个BDVoiceRecognitionClient类的属性
/**
 语音识别代理
 */
@property (nonatomic,strong)BDVoiceRecognitionClient *client;
  • 对它进行懒加载
- (BDVoiceRecognitionClient *)client {
    if (!_client) {
        _client = [BDVoiceRecognitionClient sharedInstance];
        [_client setPropertyList:@[@(EVoiceRecognitionPropertyMusic),                  // 音乐
                                   @(EVoiceRecognitionPropertyVideo),                  // 视频
                                   @(EVoiceRecognitionPropertyApp),                    // 应用
                                   @(EVoiceRecognitionPropertyWeb),                    // web
                                   @(EVoiceRecognitionPropertySearch),                 // 热词
                                   @(EVoiceRecognitionPropertyEShopping),              // 电商&购物
                                   @(EVoiceRecognitionPropertyHealth),                 // 健康&母婴
                                   @(EVoiceRecognitionPropertyCall),                   // 打电话
                                   @(EVoiceRecognitionPropertyMedicalCare) ,           // 医疗
                                   @(EVoiceRecognitionPropertyCar),                    // 汽车
                                   @(EVoiceRecognitionPropertyCatering),               // 娱乐餐饮
                                   @(EVoiceRecognitionPropertyFinanceAndEconomics),    // 财经
                                   @(EVoiceRecognitionPropertyGame),                   // 游戏
                                   @(EVoiceRecognitionPropertyCookbook),               // 菜谱
                                   @(EVoiceRecognitionPropertyAssistant),              // 助手
                                   @(EVoiceRecognitionPropertyRecharge),               // 话费充值
                                   /* 离线垂类 */
                                   @(EVoiceRecognitionPropertyContacts) ,              // 联系人指令
                                   @(EVoiceRecognitionPropertySetting),                // 手机设置
                                   @(EVoiceRecognitionPropertyTVInstruction),          // 电视指令
                                   @(EVoiceRecognitionPropertyPlayerInstruction),      // 播放器指令
                                   @(EVoiceRecognitionPropertyRadio)]];
        /* 设置识别语言为中文 */
        [_client setLanguage:EVoiceRecognitionLanguageChinese];
        /* 设置不禁用标点符号 */
        [_client disablePuncs:NO];
        /* 设置对语音进行端点检测 */
        [_client setNeedVadFlag:YES];
        /* 设置对上传的语音进行压缩 */
        [_client setNeedCompressFlag:YES];
        /* 设置在线识别的响应等待时间,如果超时,触发同步离线识别 */
        [_client setOnlineWaitTime:5];
        [_client setPlayTone:EVoiceRecognitionPlayTonesRecStart isPlay:YES];
        [_client setPlayTone:EVoiceRecognitionPlayTonesRecEnd isPlay:YES];
        /* 在开始识别前,需要加载离线识别引擎, 需要传入授权信息、语言模型文件信息及语法槽信息 */
        /* 识别垂类语法槽信息 */
        
        NSDictionary* recogGrammSlot = @{@"$name_CORE" : @"张三\n 李四\n",
                                         @"$song_CORE" : @"小苹果\n 我的滑板鞋\n", @"$app_CORE" : @"百度\n 百度地图\n",
                                         @"$artist_CORE" : @"刘德华\n 周华健\n"};
        NSString *licensePath = [[NSBundle mainBundle] pathForResource:@"bdasr_temp_license" ofType:@"dat"];
        NSString *datFilePath = [[NSBundle mainBundle] pathForResource:@"s_1" ofType:nil];
        int result = [_client loadOfflineEngine:@"8979326" license:licensePath  datFile:datFilePath LMDatFile:nil grammSlot:recogGrammSlot];
        if (result == 0) {
            NSLog(@"加载离线识别引擎成功");
        }else {
            NSLog(@"加载离线识别引擎失败");
        }
        [_client setApiKey:@"UsLZWXdC45BHo1YXMni2M4Ga" withSecretKey:@"ebb161770a7c78d2df8e46ddc4badf25"];
        
    }
    return _client;
}

其实懒加载中我们做了很多事情, 而我们进行语音识别主要也是靠BDVoiceRecognitionClient这个类的对象,
1.设置属性列表

 EVoiceRecognitionPropertyMap = 10060,  // 地图
 EVoiceRecognitionPropertyInput = 20000, // 输入

以上两个要单独使用, 不能添加到数组中,否则就会出现这个错误

EVoiceRecognitionStartWorkPropertyInvalid

2.进行各种设置

        /* 设置识别语言为中文 */
        [_client setLanguage:EVoiceRecognitionLanguageChinese];
        /* 设置不禁用标点符号 */
        [_client disablePuncs:NO];
        /* 设置对语音进行端点检测 */
        [_client setNeedVadFlag:YES];
        /* 设置对上传的语音进行压缩 */
        [_client setNeedCompressFlag:YES];
        /* 设置在线识别的响应等待时间,如果超时,触发同步离线识别 */
        [_client setOnlineWaitTime:5];
        [_client setPlayTone:EVoiceRecognitionPlayTonesRecStart isPlay:YES];
        [_client setPlayTone:EVoiceRecognitionPlayTonesRecEnd isPlay:YES];

3.在开始识别前,需要加载离线识别引擎, 需要传入授权信息、语言模型文件信息及语法槽信息

        /* 在开始识别前,需要加载离线识别引擎, 需要传入授权信息、语言模型文件信息及语法槽信息 */
        /* 识别垂类语法槽信息 */
        
        NSDictionary* recogGrammSlot = @{@"$name_CORE" : @"张三\n 李四\n",
                                         @"$song_CORE" : @"小苹果\n 我的滑板鞋\n", @"$app_CORE" : @"百度\n 百度地图\n",
                                         @"$artist_CORE" : @"刘德华\n 周华健\n"};
        NSString *licensePath = [[NSBundle mainBundle] pathForResource:@"bdasr_temp_license" ofType:@"dat"];
        NSString *datFilePath = [[NSBundle mainBundle] pathForResource:@"s_1" ofType:nil];
        int result = [_client loadOfflineEngine:@"8979326" license:licensePath  datFile:datFilePath LMDatFile:nil grammSlot:recogGrammSlot];
        if (result == 0) {
            NSLog(@"加载离线识别引擎成功");
        }else {
            NSLog(@"加载离线识别引擎失败");
        }
Note: 其中, 在方法
 - (int)loadOfflineEngine: (NSString*)appCode
                 license: (NSString*)licenseFile
                 datFile: (NSString*)datFilePath
               LMDatFile: (NSString*)LMDatFilePath
               grammSlot: (NSDictionary*)dictSlot;
中的第一个参数appCode, 就是之前在百度语音官网上注册的你的AppID
查看key

然后再设置API key 和 Secret Key

[_client setApiKey:@"你的ApiKey" withSecretKey:@"你的SecretKey"];

4.接着, 就要写一个开始语音识别的代码了, 其实也是一句代码的事情, 剩下的都是错误处理

- (void)beginVoiceRecognition {
    int beginStatus = [self.client startVoiceRecognition:self];
    switch (beginStatus) {
        case EVoiceRecognitionStartWorking:
            NSLog(@"启动成功");
            break;
        case EVoiceRecognitionStartWorkNOMicrophonePermission:
            NSLog(@"没有系统麦克风使用权限");
            break;
        case EVoiceRecognitionStartWorkNoAPIKEY:
            NSLog(@"没有ApiKey");
            break;
        case EVoiceRecognitionStartWorkGetAccessTokenFailed:
            NSLog(@"获取有效的 AccessToken 错误,需要验证开放云平台的注册信息");
            break;
        case EVoiceRecognitionStartWorkNetUnusable:
            NSLog(@"没有网络");
            break;
        case EVoiceRecognitionStartWorkDelegateInvaild:
            NSLog(@"没有实现语音回调结果");
            break;
        case EVoiceRecognitionStartWorkRecorderUnusable:
            NSLog(@"录音设备不可用");
            break;
        case EVoiceRecognitionStartWorkPreModelError:
            NSLog(@"启动预处理模块出错");
            break;
        case EVoiceRecognitionStartWorkPropertyInvalid:
            NSLog(@"识别属性设置不合法");
            break;
        default:
            break;
    }
}

然后把这个方法暴露给外界使用

5.遵守代理

<MVoiceRecognitionClientDelegate>

6.实现代理回调方法

- (void)VoiceRecognitionClientWorkStatus:(int)aStatus obj:(id)aObj {
    switch (aStatus) {
        case EVoiceRecognitionClientWorkStatusFlushData: {
            // 该状态值表示服务器返回了中间结果,如果想要将中间结果展示给用户(形成连续上屏的效果), // 可以利用与该状态同时返回的数据,每当接到新的该类消息应当清空显示区域的文字以免重复
            NSMutableString *tmpString = [[NSMutableString alloc] initWithString:@""];
            [tmpString appendFormat:@"%@",[aObj objectAtIndex:0]];
            NSLog(@"result: %@", tmpString);
            if (self.delegate && [self.delegate respondsToSelector:@selector(getVoiceToMessage:fromRecognizer:)]) {
                [self.delegate getVoiceToMessage:tmpString fromRecognizer:self];
            }
            break;
        }
        case EVoiceRecognitionClientWorkStatusFinish: {
            // 该状态值表示语音识别服务器返回了最终结果,结果以数组的形式保存在 aObj 对象中 // 接受到该消息时应当清空显示区域的文字以免重复
            if ([_client getRecognitionProperty] != EVoiceRecognitionPropertyInput) {
                NSMutableArray *resultData = (NSMutableArray *)aObj;
                NSMutableString *tmpString = [[NSMutableString alloc] initWithString:@""];
                // 获取识别候选词列表
                for (int i=0; i<[resultData count]; i++) {
                    [tmpString appendFormat:@"%@\r\n",[resultData objectAtIndex:i]]; }
                NSLog(@"result: %@", tmpString);
            } else {
                NSMutableString *sentenceString = [[NSMutableString alloc] initWithString:@""];
                for (NSArray *result in aObj) { // 此时 aObj 是 array,result 也是 array
                    // 取每条候选结果的第0条,进行组合
                    // result 的元素是 dictionary,对应每个候选词和对应的可信度
                    NSDictionary *dic = [result objectAtIndex:0];
                    NSString *candidateWord = [[dic allKeys] objectAtIndex:0];
                    [sentenceString appendString:candidateWord];
                }
                NSLog(@"result: %@", sentenceString);
            }
            break;
        }
        case EVoiceRecognitionClientWorkStatusReceiveData: {
            // 此状态只在输入模式下发生,表示语音识别正确返回结果,每个子句会通知一次(全量, // 即第二次收到该消息时所携带的结果包含第一句的识别结果),应用程序可以
            // 逐句显示。配合连续上屏的中间结果,可以进一步 升语音输入的体验
            NSMutableString *sentenceString = [[NSMutableString alloc] initWithString:@""];
            for (NSArray *result in aObj) { // 此时 aObj 是 array,result 也是 array
                // 取每条候选结果的第 条,进 组合
                // result 的元素是 dictionary,对应 个候选词和对应的可信度
                NSDictionary *dic = [result objectAtIndex:0];
                NSString *candidateWord = [[dic allKeys] objectAtIndex:0];
                [sentenceString appendString:candidateWord];
            }
            NSLog(@"result: %@", sentenceString);
            if (self.delegate && [self.delegate respondsToSelector:@selector(getVoiceToMessage:fromRecognizer:)]) {
                [self.delegate getVoiceToMessage:sentenceString fromRecognizer:self];
            }
            break;
        }
        case EVoiceRecognitionClientWorkStatusNewRecordData: {
            // 有音频数据输出,音频数据格式为 PCM,在有 WiFi 连接的条件下为 16k16bit,非 WiFi
            // 为 8k16bit
            
        }
            break;
        case EVoiceRecognitionClientWorkStatusEnd: {
            // 用户说话完成,但服务器尚未返回结果
            NSLog(@"用户说话完成,但服务器尚未返回结果");
            MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow.rootViewController.view animated:YES];
            hud.mode = MBProgressHUDModeCustomView;
            hud.labelText = @"正在语音转文字中,请稍候";
            [hud hide:YES afterDelay:2];
        }
            break;
            
        case EVoiceRecognitionClientWorkStatusCancel: {
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusError: {
            MBProgressHUD *hud = [MBProgressHUD showHudTo:[UIApplication sharedApplication].keyWindow.rootViewController.view image:nil text:@"语音转文字失败,请重试" animated:YES];
            hud.mode = MBProgressHUDModeCustomView;
            [hud hide:YES afterDelay:2];
            NSLog(@"语音转文字发生错误");
            break;
        }
        case EVoiceRecognitionClientWorkPlayStartTone:
        case EVoiceRecognitionClientWorkPlayStartToneFinish:
        case EVoiceRecognitionClientWorkStatusStartWorkIng:
        case EVoiceRecognitionClientWorkStatusStart:
        case EVoiceRecognitionClientWorkPlayEndToneFinish:
        case EVoiceRecognitionClientWorkPlayEndTone: ;
    }
}

在上面这个方法实现中, 我将语音识别的结果传给我本类的代理方法

@class ZHLVoiceRecognizer;
@protocol ZHLVoiceRecognizerDelegate <NSObject>
@optional

- (void)getVoiceToMessage:(NSString *)message fromRecognizer:(ZHLVoiceRecognizer *)recognizer;

@end

7.这样,我在其他类中遵守ZHLVoiceRecognizerDelegate这个代理, 实现代理方法时, 就能拿到语音识别的结果, 就是那个字符串啦

比如,在我们公司的项目中, 我就将其赋值给textView.text,这样就用户就可以看到语音识别的结果了

- (void)getVoiceToMessage:(NSString *)message fromRecognizer:(ZHLVoiceRecognizer *)recognizer {
    self.textView.text = message;
}
给大家看看实现效果, 我觉得语音识别的准确率是很高的,请原谅我不是很标准的普通话
实现效果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容