2016-11-25 21:32:40 wanwanchengxuyuan 阅读数 826

Hololens上语音输入有三种形式,分别是:
- 语音命令 Voice Command
- 听写 Diction
- 语法识别 Grammar Recognizer

HoloLens开发手记 - 语音识别(语音命令) 博客已经介绍了 Voice Command 的用法。本文将介绍听写的用法:

听写识别 Diction


听写就是语音转化成文字 (Speech to Text)。此特性在HoloLens上使用的场所一般多用于需要用到键入文字的地方,例如在HoloLens中使用 Edge 搜索时,由于在HoloLens上一般是非常规的物理键盘输入,使用手势点按虚拟键盘键入文字的具体操作需要用户转动头部将Gaze射线光标定位到想输入的虚拟键盘字母上,再用Gesture点按手势确认选定此字母,由此可见还是有极大的不便性。

Paste_Image.png

所以语音转为文字实现键入内容的操作将能大大提高效率。

听写特性用于将用户语音转为文字输入,同时支持内容推断和事件注册特性。Start()和Stop()方法用于启用和禁用听写功能,在听写结束后需要调用Dispose()方法来关闭听写页面。GC会自动回收它的资源,如果不Dispose会带来额外的性能开销。

使用听写识别应该注意的是:
1. 在你的应用中必须打开 Microphone  特性。设置如下:Edit -> Project Settings -> Player -> Windows Store -> Publishing Settings > Capabilities 中确认勾上Microphone。
2. 必须确认HoloLens连接上了wifi,这样听写识别才能工作。

DictationRecognizer.cs

using HoloToolkit;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Windows.Speech;

public class MicrophoneManager : MonoBehaviour
{
    [Tooltip("A text area for the recognizer to display the recognized strings.")]
    public Text DictationDisplay;

    private DictationRecognizer dictationRecognizer;

    // Use this string to cache the text currently displayed in the text box.
    //使用此字符串可以缓存当前显示在文本框中的文本。
    private StringBuilder textSoFar;

    void Awake()
    {
        /* TODO: DEVELOPER CODING EXERCISE 3.a */

        //Create a new DictationRecognizer and assign it to dictationRecognizer variable.
        dictationRecognizer = new DictationRecognizer();

        //Register for dictationRecognizer.DictationHypothesis and implement DictationHypothesis below
        // This event is fired while the user is talking. As the recognizer listens, it provides text of what it's heard so far.
        //注册听写假设事件。此事件在用户说话时触发。当识别器收听时,提供到目前为止所听到的内容文本
        dictationRecognizer.DictationHypothesis += DictationRecognizer_DictationHypothesis;

        //Register for dictationRecognizer.DictationResult and implement DictationResult below
        // This event is fired after the user pauses, typically at the end of a sentence. The full recognized string is returned here.
        //注册听写结果事件。此事件在用户暂停后触发,通常在句子的结尾处,返回完整的已识别字符串
        dictationRecognizer.DictationResult += DictationRecognizer_DictationResult;

        //Register for dictationRecognizer.DictationComplete and implement DictationComplete below
        // This event is fired when the recognizer stops, whether from Stop() being called, a timeout occurring, or some other error.
        //注册听写完成事件。无论是调用Stop()函数、发生超时或者其他的错误使得识别器停止都会触发此事件
        dictationRecognizer.DictationComplete += DictationRecognizer_DictationComplete;

        //Register for dictationRecognizer.DictationError and implement DictationError below
        // This event is fired when an error occurs.
        //注册听写错误事件。当发生错误时调用此事件,通常是为连接网络或者在识别过程中网络发生中断等时产生错误
        dictationRecognizer.DictationError += DictationRecognizer_DictationError;

        // Shutdown the PhraseRecognitionSystem. This controls the KeywordRecognizers
        //PhraseRecognitionSystem控制的是KeywordRecognizers,关闭语音命令关键字识别。只有在关闭这个后才能开启听写识别
        PhraseRecognitionSystem.Shutdown();

        //Start dictationRecognizer
        //开启听写识别
        dictationRecognizer.Start();

    }

    /// <summary>
    /// This event is fired while the user is talking. As the recognizer listens, it provides text of what it's heard so far.
    /// </summary>
    /// <param name="text">The currently hypothesized recognition.</param>
    private void DictationRecognizer_DictationHypothesis(string text)
    {
        // Set DictationDisplay text to be textSoFar and new hypothesized text
        // We don't want to append to textSoFar yet, because the hypothesis may have changed on the next event
        DictationDisplay.text = textSoFar.ToString() + " " + text + "...";
    }

    /// <summary>
    /// This event is fired after the user pauses, typically at the end of a sentence. The full recognized string is returned here.
    /// </summary>
    /// <param name="text">The text that was heard by the recognizer.</param>
    /// <param name="confidence">A representation of how confident (rejected, low, medium, high) the recognizer is of this recognition.</param>
    private void DictationRecognizer_DictationResult(string text, ConfidenceLevel confidence)
    {
        // 3.a: Append textSoFar with latest text
        textSoFar.Append(text + "");

        // 3.a: Set DictationDisplay text to be textSoFar
        DictationDisplay.text = textSoFar.ToString();
    }

    /// <summary>
    /// This event is fired when the recognizer stops, whether from Stop() being called, a timeout occurring, or some other error.
    /// Typically, this will simply return "Complete". In this case, we check to see if the recognizer timed out.
    /// </summary>
    /// <param name="cause">An enumerated reason for the session completing.</param>
    private void DictationRecognizer_DictationComplete(DictationCompletionCause cause)
    {
        // If Timeout occurs, the user has been silent for too long.
        // With dictation, the default timeout after a recognition is 20 seconds.
        // The default timeout with initial silence is 5 seconds.
        //如果在听写开始后第一个5秒内没听到任何声音,将会超时  
        //如果识别到了一个结果但是之后20秒没听到任何声音,也会超时  
        if (cause == DictationCompletionCause.TimeoutExceeded)
        {
            Microphone.End(deviceName);

            DictationDisplay.text = "Dictation has timed out. Please press the record button again.";
            SendMessage("ResetAfterTimeout");
        }
    }

    /// <summary>
    /// This event is fired when an error occurs.
    /// </summary>
    /// <param name="error">The string representation of the error reason.</param>
    /// <param name="hresult">The int representation of the hresult.</param>
    private void DictationRecognizer_DictationError(string error, int hresult)
    {
        // 3.a: Set DictationDisplay text to be the error string
        DictationDisplay.text = error + "\nHRESULT: " + hresult;
    }


    // Update is called once per frame  
    void Update () {  

    }  

    void OnDestroy()  
    {  
        dictationRecognizer.Stop();  
        dictationRecognizer.DictationHypothesis -= DictationRecognizer_DictationHypothesis;  
        dictationRecognizer.DictationResult -= DictationRecognizer_DictationResult;  
        dictationRecognizer.DictationComplete -= DictationRecognizer_DictationComplete;  
        dictationRecognizer.DictationError -= DictationRecognizer_DictationError;  
        dictationRecognizer.Dispose();  
    }  

}

HoloLens只能运行单个语音识别 (run at a time),所以若要使用听写识别的话,必须要关闭KeywordRecognizer

DictationRecognizer中设置有两个超时
1. 如果识别器启用并且在5秒内没有听到任何声音,将会超时。
2. 如果识别器识别到了结果,但是在20秒内没有听到声音,将会超时。

2014-03-31 00:45:29 PullThin_Youth 阅读数 3368

      接触android几个月以来第一次写博客,有点小激动,相信这一举动会慢慢见证我在IT路上的成长;由于最近项目中用到了语音识别技术,乘着周末有时间记录下来,本人新手一枚,高手勿喷 ^_^


我这里用的是科大讯飞语音API,具体步骤如下:

1、首先进入科大讯飞语音云平台http://open.voicecloud.cn/,注册一个开发者账号

2、创建新应用,得到一个app_id号,然后下载android平台SDK

3、开始创建我们自己的应用,项目中需要在lib文件夹下加入Msc.jar和libmsc.so两个文件,注意libmsc.so是在armeabi文件夹下


布局界面和运行界面



在工程AndroidManifest.xml 文件中添加如下权限

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

布局文件代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xj.audiodemo.MainActivity"
    android:orientation="vertical"
    tools:ignore="MergeRootFrame" >
    
    <TextView 
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:background="@drawable/titlebar_bg"
        android:text="@string/audio_write"
        android:textSize="25dp"
        />
    
    <EditText 
        android:id="@+id/et_content"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_width="match_parent"
        android:gravity="top"
        android:layout_height="180dp"
        android:background="@drawable/logo"
        />
    <Button
       	android:id="@+id/bt_start"
        android:layout_marginTop="10dp" 
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/start_audio"
        android:background="@drawable/btn_effect"
        />
	
</LinearLayout>

MainActivity代码

package com.xj.audiodemo;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;

import com.iflytek.cloud.speech.RecognizerResult;
import com.iflytek.cloud.speech.SpeechConstant;
import com.iflytek.cloud.speech.SpeechError;
import com.iflytek.cloud.speech.SpeechListener;
import com.iflytek.cloud.speech.SpeechUser;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;
import com.xj.audiodemo.util.JsonParser;

public class MainActivity extends ActionBarActivity implements OnClickListener {
	private EditText editText;
	//听写dialog
	private RecognizerDialog recognizerDialog;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);
		
		loginAudio();
		init();
	}

	private void loginAudio() {
		SpeechUser.getUser().login(this, null, null, "appid="+getString(R.string.app_id), listener);
	}

	private void init() {
		editText = (EditText) findViewById(R.id.et_content);
		findViewById(R.id.bt_start).setOnClickListener(this);
		
		//创建听写dialog
		recognizerDialog = new RecognizerDialog(this);
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.bt_start://开始录音
			showAduioDialog();
			break;
		}
	}
	
	/**
	 * 显示听写dialog
	 */
	private void showAduioDialog(){
		if(null != recognizerDialog){
			recognizerDialog = new RecognizerDialog(this);
		}
		//清空Grammar_ID,防止识别后进行听写时Grammar_ID的干扰
		recognizerDialog.setParameter(SpeechConstant.CLOUD_GRAMMAR, null);
		//设置听写dialog的引擎
		recognizerDialog.setParameter(SpeechConstant.DOMAIN, getString(R.string.engine));
		//设置采样率参数  支持8k 和 16k
		recognizerDialog.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
		//清空显示框
		editText.setText(null);
		//显示听写对话框
		recognizerDialog.setListener(dialogListener);
		recognizerDialog.show();
	}
	
	private RecognizerDialogListener dialogListener = new RecognizerDialogListener() {
		
		//识别返回结果
		@Override 
		public void onResult(RecognizerResult results, boolean isLast) {
			//results返回的是json串 需要解析
			String text = JsonParser.parseIatResult(results.getResultString());
			editText.append(text);
			editText.setSelection(editText.length());
		}
		
		//识别回调错误
		@Override 
		public void onError(SpeechError error) {
			
		}
	};
	
	/**
	 * 用户的登陆的回调函数
	 */
	private SpeechListener listener = new SpeechListener() {
		
		@Override
		public void onEvent(int arg0, Bundle arg1) {}
		
		@Override
		public void onData(byte[] arg0) {}
		
		//登陆完成返回的结果
		@Override
		public void onCompleted(SpeechError error) {
			if(null != error){
				Toast.makeText(MainActivity.this, getString(R.string.login_error), Toast.LENGTH_SHORT).show();
			}else{
				Toast.makeText(MainActivity.this, getString(R.string.login_success), Toast.LENGTH_SHORT).show();
			}
		}
	};
	
	@Override
	protected void onStop() {
		super.onStop();
		if(null != recognizerDialog){
			recognizerDialog.cancel();
		}
	}

}


Util工具类 JSON解析

package com.xj.audiodemo.util;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.text.TextUtils;

/**
 * @author x j
 */
public class JsonParser {
	
	/**
	 * 听写结果的Json格式解析
	 * @param json
	 * @return string
	 */
	public static String parseIatResult(String json) {
		if(TextUtils.isEmpty(json))
			return "";
		
		StringBuffer ret = new StringBuffer();
		try {
			JSONTokener tokener = new JSONTokener(json);
			JSONObject joResult = new JSONObject(tokener);

			JSONArray words = joResult.getJSONArray("ws");
			for (int i = 0; i < words.length(); i++) {
				JSONArray items = words.getJSONObject(i).getJSONArray("cw");// 听写结果词,默认使用第一个结果
				JSONObject obj = items.getJSONObject(0);
				ret.append(obj.getString("w"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} 
		return ret.toString();
	}
}

本程序员自己开发的两款小应用,欢迎大家试玩 

《史上最牛的手相大师》 在安卓市场,91,百度手机助手,360手机助手,豌豆荚,应用宝已上线

豌豆荚 http://www.wandoujia.com/apps/com.example.handmoney

安卓市场 http://apk.hiapk.com/appinfo/com.example.handmoney

百度手机助手 http://as.baidu.com/a/item?docid=6800908&pre=web_am_se

《史上最牛的人脸评分仪》 在 在安卓市场,91,百度手机助手,360手机助手,豌豆荚已上线

豌豆荚 http://www.wandoujia.com/apps/com.xj.facerecognition

安卓市场 http://apk.hiapk.com/appinfo/com.xj.facerecognition

百度手机助手 http://as.baidu.com/a/item?docid=6802582&pre=web_am_se









                                
2018-03-15 16:55:57 u012375207 阅读数 502

写在前面

时隔一年没有写博客了,最近的一篇博客是2017年4月17日,怎么说呢,一个字,懒。哈哈,当然实际上是从去年四月开始接到做语音识别的项目去了,一直到现在也没什么空(其实有空,但是还是懒)。被各种bug折磨了一整年之后,终于也算是熬出头了,这里就写一个关于语音框架构建的博客,不过因为公司项目,肯定不能说得太详细了,说个大概吧,具体代码之类的可以去看看百度的那个duros,虽然我觉得它写得很烂。

关于语音识别

语音识别是一个很庞大的东西,当然对于我们这样的客户端选手来说,需要了解的也不多,这里简单的说一下,因为很庞大,所以篇幅原因,我就不说太详细(其实还是因为懒)。

云端识别

语音识别因为需要很大的数据量作为支持,所以要得到很好的体验,只能通过服务器跑神经网络来实现,所以大部分的识别是放在云端的,这里本地只需要做好网络和录放音即可。然后语音识别分两种,一种叫做听写机,还有一种叫做JSGF。他们两个的区别就在于听写机是你随便说什么,他都能给你识别出来,对错就不知道,也没有一个固定的值,JSGF就只能够识别固定的词语,你设置了他就能识别,没有设置他就不能识别。这里你就可以看看Siri的实现,当你对Siri说打电话给张三的时候,“打电话给”这四个字就是云端听写机实现的识别,“张三”这两个字就是JSGF识别的,因为张三在当前情况下一定是一个人名,那么它就会拿这个语音到你的通讯录,通过JSGF来识别。JSGF简单的说就是在一个固定的结果集里面,一定会识别出一个结果给你。如果这里我没解释清楚的,自己去百度看看,记住,就是两种东西:听写机和JSGF。

本地识别

由于刚才说了识别就分两种,一种是听写机,还有一种是JSGF,因为听写机需要很庞大的数据支撑,所以本地只能使用JSGF,当然这也至少有二三十兆。JSGF放在本地一般用作唤醒,比如类似hey Siri这种操作,当然Siri还做了声纹识别。还用作识别第一个、第二个这种类似选择的命令词。

端点检测

这个用作检测当前是否有人说话。

回声消除

回声消除主要用在当设备在播放音频的同时还要录音识别的时候,因为这种时候mic会把播放出来的音频一起录制回去,所以需要做一个消除的算法,由于android手机的AudioTrack和AudioRecord中都存在一个环形缓冲区,而每一个厂家的缓冲区大小不同,所以会造成录制的音频和当前播放的音频不一致,音频起点无法对齐,而出现消除效果很差的情况,所以这个功能在Android手机上是一个难以突破的瓶颈,不过可以勉强使用系统自带的communication来实现,不过很多厂家可能是由于技术原因吧,这个communication的参数根本没用,这里点名小米,小米垃圾,哈哈哈。

TTS

TTS就是把文字生成为音频的一个东西,也分为云端和本地,当然是云端效果好,不过本地也有它的作用,比如说没有网络的时候。

SILK

这是一个音频压缩库,主要用于本地和服务器通信的时候对数据做压缩处理。

正文开始咯

思路

怎么说呢,这个东西是一个很庞大的,一时半儿说不清楚,这里涉及太多东西了,我先挨个说怎么做,至于为什么这样做,能解释的就解释,不能解释的,就先挨个去了解这些知识,最后就知道为什么了。

分包

一共分4个包:

  1. SILK音频压缩
  2. TTS+音频播放
  3. 本地识别+端点检测+回声消除+音频录制
  4. 云端识别

本地识别包的实现

一个录音数据同步队列
一个识别结果同步队列
一个录音线程
一个识别线程
内部流程:录音线程录制音频放入录音队列,识别线程从录音线程取出数据识别后放入结果队列
外部调用:从结果队列取出结果进行操作
结果数据包括:识别内容、音频原始数据、端点数据

TTS包的实现

一个生成同步队列
一个结果同步队列
一个生成线程
一个播放线程
内部流程:生成线程从生成队列中取出文字进行生成,生成的音频放进结果队列,播放线程从结果队列中取出音频进行播放
外部调用:需要传入interface从内部回调播放状态

云端识别的实现

云端识别需要依赖本地识别库,然后从本地识别库中取数据往云端发,这里需要一个网络队列即可。

具体业务实现

架构

采用MVP架构,能够做到多个子流程解耦,比如说在Siri中,播放音乐和拨打电话,这能够分为两个子流程,他们和主流程(对话流程)是毫不相干的,但是他们却是操作的同一个页面,那么这里我们能够联想到MVP架构,我只需要在切换流程的时候切换不同的presenter即可,页面不用进行其他操作,而且每个presenter中的业务也毫不相干,可以多人共同协助开发。这里的业务分发根据云端识别后进行语义分析返回的业务标签来判断,这里我们可以考虑做成注解的方式,子流程在类名上注解自己处理的业务标签,主流程在得到业务标签后进行遍历,反射调用子流程,这样的话完完全全能够模仿Spring的操作了。

然后我感觉好像也没什么可说的了,因为涉及到太多不能说的东西,而且还有很多单独拿出来都很庞大的东西,不是三言两语能说清楚的,比如说JSGF,这里只是提供一个思路,提供一个方向,按照这个放下往下走,是可以构建出一个语音识别的开发框架的。你们可以找一下各种语音开放平台,下载这些库,来根据我这个大概的思路进行实现。

2016-04-10 11:39:55 qq_31810357 阅读数 14955


原文地址http://blog.csdn.net/qq_31810357/article/details/51111702

前言:最近研究了一下语音识别,从百度语音识别到讯飞语音识别;首先说一下个人针对两者的看法,讯飞毫无疑问比较专业,识别率也很高真对语音识别是比较精准的,但是很多开发者和我一样期望离线识别,而讯飞离线是收费的;请求次数来讲,两者都可以申请高配额,针对用户较多的几乎都一样。基于免费并且支持离线我选择了百度离线语音识别。比较简单,UI设计多一点,下面写一下教程:

1.首先:需要的库

2.我是自定义的UI所以以功能实现为主(头文件)

// 头文件
#import "BDVRCustomRecognitonViewController.h"
#import "BDVRClientUIManager.h"
#import "WBVoiceRecordHUD.h"
#import "BDVRViewController.h"
#import "MyViewController.h"
#import "BDVRSConfig.h"

3.需要知道的功能:能用到的如下:

//-------------------类方法------------------------
// 创建语音识别客户对像,该对像是个单例
+ (BDVoiceRecognitionClient *)sharedInstance;

// 释放语音识别客户端对像
+ (void)releaseInstance;


//-------------------识别方法-----------------------
// 判断是否可以录音
- (BOOL)isCanRecorder;

// 开始语音识别,需要实现MVoiceRecognitionClientDelegate代理方法,并传入实现对像监听事件
// 返回值参考 TVoiceRecognitionStartWorkResult
- (int)startVoiceRecognition:(id<MVoiceRecognitionClientDelegate>)aDelegate;

// 说完了,用户主动完成录音时调用
- (void)speakFinish;

// 结束本次语音识别
- (void)stopVoiceRecognition;

/**
 * @brief 获取当前识别的采样率
 *
 * @return 采样率(16000/8000)
 */
- (int)getCurrentSampleRate;

/**
 * @brief 得到当前识别模式(deprecated)
 *
 * @return 当前识别模式
 */
- (int)getCurrentVoiceRecognitionMode __attribute__((deprecated));

/**
 * @brief 设置当前识别模式(deprecated),请使用-(void)setProperty:(TBDVoiceRecognitionProperty)property;
 *
 * @param 识别模式
 *
 * @return 是否设置成功
 */
- (void)setCurrentVoiceRecognitionMode:(int)aMode __attribute__((deprecated));

// 设置识别类型
- (void)setProperty:(TBDVoiceRecognitionProperty)property __attribute__((deprecated));

// 获取当前识别类型
- (int)getRecognitionProperty __attribute__((deprecated));

// 设置识别类型列表, 除EVoiceRecognitionPropertyInput和EVoiceRecognitionPropertySong外
// 可以识别类型复合
- (void)setPropertyList: (NSArray*)prop_list;

// cityID仅对EVoiceRecognitionPropertyMap识别类型有效
- (void)setCityID: (NSInteger)cityID;

// 获取当前识别类型列表
- (NSArray*)getRecognitionPropertyList;

//-------------------提示音-----------------------
// 播放提示音,默认为播放,录音开始,录音结束提示音
// BDVoiceRecognitionClientResources/Tone
// record_start.caf   录音开始声音文件
// record_end.caf     录音结束声音文件
// 声音资源需要加到项目工程里,用户可替换资源文件,文件名不可以变,建音提示音不宜过长,0。5秒左右。
// aTone 取值参考 TVoiceRecognitionPlayTones,如没有找到文件,则返回NO
- (BOOL)setPlayTone:(int)aTone isPlay:(BOOL)aIsPlay;

4.录音按钮相关动画(我自定义的,大家可以借鉴)


// 录音按钮相关
@property (nonatomic, weak, readonly) UIButton *holdDownButton;// 说话按钮
/**
 *  是否取消錄音
 */
@property (nonatomic, assign, readwrite) BOOL isCancelled;

/**
 *  是否正在錄音
 */
@property (nonatomic, assign, readwrite) BOOL isRecording;
/**
 *  当录音按钮被按下所触发的事件,这时候是开始录音
 */
- (void)holdDownButtonTouchDown;

/**
 *  当手指在录音按钮范围之外离开屏幕所触发的事件,这时候是取消录音
 */
- (void)holdDownButtonTouchUpOutside;

/**
 *  当手指在录音按钮范围之内离开屏幕所触发的事件,这时候是完成录音
 */
- (void)holdDownButtonTouchUpInside;

/**
 *  当手指滑动到录音按钮的范围之外所触发的事件
 */
- (void)holdDownDragOutside;

5.初始化系统UI

#pragma mark - layout subViews UI

/**
 *  根据正常显示和高亮状态创建一个按钮对象
 *
 *  @param image   正常显示图
 *  @param hlImage 高亮显示图
 *
 *  @return 返回按钮对象
 */
- (UIButton *)createButtonWithImage:(UIImage *)image HLImage:(UIImage *)hlImage ;
- (void)holdDownDragInside;
- (void)createInitView; // 创建初始化界面,播放提示音时会用到
- (void)createRecordView;  // 创建录音界面
- (void)createRecognitionView; // 创建识别界面
- (void)createErrorViewWithErrorType:(int)aStatus; // 在识别view中显示详细错误信息
- (void)createRunLogWithStatus:(int)aStatus; // 在状态view中显示详细状态信息

- (void)finishRecord:(id)sender; // 用户点击完成动作
- (void)cancel:(id)sender; // 用户点击取消动作

- (void)startVoiceLevelMeterTimer;
- (void)freeVoiceLevelMeterTimerTimer;

6.最重要的部分


// 录音完成
 [[BDVoiceRecognitionClient sharedInstance] speakFinish];

// 取消录音
[[BDVoiceRecognitionClient sharedInstance] stopVoiceRecognition];

7.两个代理方法

- (void)VoiceRecognitionClientWorkStatus:(int)aStatus obj:(id)aObj
{
    switch (aStatus)
    {
        case EVoiceRecognitionClientWorkStatusFlushData: // 连续上屏中间结果
        {
            NSString *text = [aObj objectAtIndex:0];
            
            if ([text length] > 0)
            {
//                [clientSampleViewController logOutToContinusManualResut:text];
                
                UILabel *clientWorkStatusFlushLabel = [[UILabel alloc]initWithFrame:CGRectMake(kScreenWidth/2 - 100,64,200,60)];
                clientWorkStatusFlushLabel.text = text;
                clientWorkStatusFlushLabel.textAlignment = NSTextAlignmentCenter;
                clientWorkStatusFlushLabel.font = [UIFont systemFontOfSize:18.0f];
                clientWorkStatusFlushLabel.numberOfLines = 0;
                clientWorkStatusFlushLabel.backgroundColor = [UIColor whiteColor];
                [self.view addSubview:clientWorkStatusFlushLabel];
                
            }

            break;
        }
        case EVoiceRecognitionClientWorkStatusFinish: // 识别正常完成并获得结果
        {
			[self createRunLogWithStatus:aStatus];
            
            if ([[BDVoiceRecognitionClient sharedInstance] getRecognitionProperty] != EVoiceRecognitionPropertyInput)
            {
                //  搜索模式下的结果为数组,示例为
                // ["公园", "公元"]
                NSMutableArray *audioResultData = (NSMutableArray *)aObj;
                NSMutableString *tmpString = [[NSMutableString alloc] initWithString:@""];
                
                for (int i=0; i < [audioResultData count]; i++)
                {
                    [tmpString appendFormat:@"%@\r\n",[audioResultData objectAtIndex:i]];
                }
                
                clientSampleViewController.resultView.text = nil;
                [clientSampleViewController logOutToManualResut:tmpString];
                
            }
            else
            {
                NSString *tmpString = [[BDVRSConfig sharedInstance] composeInputModeResult:aObj];
                [clientSampleViewController logOutToContinusManualResut:tmpString];
                
            }
           
            if (self.view.superview)
            {
                [self.view removeFromSuperview];
            }
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusReceiveData:
        {
            // 此状态只有在输入模式下使用
            // 输入模式下的结果为带置信度的结果,示例如下:
            //  [
            //      [
            //         {
            //             "百度" = "0.6055192947387695";
            //         },
            //         {
            //             "摆渡" = "0.3625582158565521";
            //         },
            //      ]
            //      [
            //         {
            //             "一下" = "0.7665404081344604";
            //         }
            //      ],
            //   ]
//暂时关掉 -- 否则影响跳转结果
//            NSString *tmpString = [[BDVRSConfig sharedInstance] composeInputModeResult:aObj];
//            [clientSampleViewController logOutToContinusManualResut:tmpString];
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusEnd: // 用户说话完成,等待服务器返回识别结果
        {
			[self createRunLogWithStatus:aStatus];
            if ([BDVRSConfig sharedInstance].voiceLevelMeter)
            {
                [self freeVoiceLevelMeterTimerTimer];
            }
			
            [self createRecognitionView];
            
            break;
        }
        case EVoiceRecognitionClientWorkStatusCancel:
        {            
            if ([BDVRSConfig sharedInstance].voiceLevelMeter) 
            {
                [self freeVoiceLevelMeterTimerTimer];
            }
            
			[self createRunLogWithStatus:aStatus];  
            
            if (self.view.superview) 
            {
                [self.view removeFromSuperview];
            }
            break;
        }
        case EVoiceRecognitionClientWorkStatusStartWorkIng: // 识别库开始识别工作,用户可以说话
        {
            if ([BDVRSConfig sharedInstance].playStartMusicSwitch) // 如果播放了提示音,此时再给用户提示可以说话
            {
                [self createRecordView];
            }
            
            if ([BDVRSConfig sharedInstance].voiceLevelMeter)  // 开启语音音量监听
            {
                [self startVoiceLevelMeterTimer];
            }
            
			[self createRunLogWithStatus:aStatus]; 

            break;
        }
		case EVoiceRecognitionClientWorkStatusNone:
		case EVoiceRecognitionClientWorkPlayStartTone:
		case EVoiceRecognitionClientWorkPlayStartToneFinish:
		case EVoiceRecognitionClientWorkStatusStart:
		case EVoiceRecognitionClientWorkPlayEndToneFinish:
		case EVoiceRecognitionClientWorkPlayEndTone:
		{
			[self createRunLogWithStatus:aStatus];
			break;
		}
        case EVoiceRecognitionClientWorkStatusNewRecordData:
        {
            break;
        }
        default:
        {
			[self createRunLogWithStatus:aStatus];
            if ([BDVRSConfig sharedInstance].voiceLevelMeter) 
            {
                [self freeVoiceLevelMeterTimerTimer];
            }
            if (self.view.superview) 
            {
                [self.view removeFromSuperview];
            }
 
            break;
        }
    }
}

- (void)VoiceRecognitionClientNetWorkStatus:(int) aStatus
{
    switch (aStatus) 
    {
        case EVoiceRecognitionClientNetWorkStatusStart:
        {	
			[self createRunLogWithStatus:aStatus];
            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
            break;   
        }
        case EVoiceRecognitionClientNetWorkStatusEnd:
        {
			[self createRunLogWithStatus:aStatus];
			[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
            break;   
        }          
    }
}

8.录音按钮的一些操作


#pragma mark ------ 关于按钮操作的一些事情-------
- (void)holdDownButtonTouchDown {
    // 开始动画
    _disPlayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(delayAnimation)];
    _disPlayLink.frameInterval = 40;
    [_disPlayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    
    self.isCancelled = NO;
    self.isRecording = NO;
    
 // 开始语音识别功能,之前必须实现MVoiceRecognitionClientDelegate协议中的VoiceRecognitionClientWorkStatus:obj方法
    int startStatus = -1;
    startStatus = [[BDVoiceRecognitionClient sharedInstance] startVoiceRecognition:self];
    if (startStatus != EVoiceRecognitionStartWorking) // 创建失败则报告错误
    {
        NSString *statusString = [NSString stringWithFormat:@"%d",startStatus];
        [self performSelector:@selector(firstStartError:) withObject:statusString afterDelay:0.3];  // 延迟0.3秒,以便能在出错时正常删除view
        return;
    }
    // "按住说话-松开搜索"提示
    [voiceImageStr removeFromSuperview];
    voiceImageStr = [[UIImageView alloc]initWithFrame:CGRectMake(kScreenWidth/2 - 40, kScreenHeight - 153, 80, 33)];
    voiceImageStr.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"searchVoice"]];
    [self.view addSubview:voiceImageStr];
   
}

- (void)holdDownButtonTouchUpOutside {
    // 结束动画
    [self.view.layer removeAllAnimations];
    [_disPlayLink invalidate];
    _disPlayLink = nil;
    
    // 取消录音
    [[BDVoiceRecognitionClient sharedInstance] stopVoiceRecognition];
    
    if (self.view.superview)
    {
        [self.view removeFromSuperview];
    }
}

- (void)holdDownButtonTouchUpInside {
    // 结束动画
    [self.view.layer removeAllAnimations];
    [_disPlayLink invalidate];
    _disPlayLink = nil;
    
    [[BDVoiceRecognitionClient sharedInstance] speakFinish];
}

- (void)holdDownDragOutside {
    
    //如果已經開始錄音了, 才需要做拖曳出去的動作, 否則只要切換 isCancelled, 不讓錄音開始.
    if (self.isRecording) {
//        if ([self.delegate respondsToSelector:@selector(didDragOutsideAction)]) {
//            [self.delegate didDragOutsideAction];
//        }
    } else {
        self.isCancelled = YES;
    }
}


#pragma mark - layout subViews UI

- (UIButton *)createButtonWithImage:(UIImage *)image HLImage:(UIImage *)hlImage {
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(kScreenWidth/2 -36, kScreenHeight - 120, 72, 72)];
    
    if (image)
        [button setBackgroundImage:image forState:UIControlStateNormal];
    if (hlImage)
        [button setBackgroundImage:hlImage forState:UIControlStateHighlighted];
    
    return button;
}

#pragma mark ----------- 动画部分 -----------
- (void)startAnimation
{
    CALayer *layer = [[CALayer alloc] init];
    layer.cornerRadius = [UIScreen mainScreen].bounds.size.width/2;
    layer.frame = CGRectMake(0, 0, layer.cornerRadius * 2, layer.cornerRadius * 2);
    layer.position = CGPointMake([UIScreen mainScreen].bounds.size.width/2,[UIScreen mainScreen].bounds.size.height - 84);
    //    self.view.layer.position;
    UIColor *color = [UIColor colorWithRed:arc4random()%10*0.1 green:arc4random()%10*0.1 blue:arc4random()%10*0.1 alpha:1];
    layer.backgroundColor = color.CGColor;
    [self.view.layer addSublayer:layer];
    
    CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    
    _animaTionGroup = [CAAnimationGroup animation];
    _animaTionGroup.delegate = self;
    _animaTionGroup.duration = 2;
    _animaTionGroup.removedOnCompletion = YES;
    _animaTionGroup.timingFunction = defaultCurve;
    
    CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
    scaleAnimation.fromValue = @0.0;
    scaleAnimation.toValue = @1.0;
    scaleAnimation.duration = 2;
    
    CAKeyframeAnimation *opencityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    opencityAnimation.duration = 2;
    opencityAnimation.values = @[@0.8,@0.4,@0];
    opencityAnimation.keyTimes = @[@0,@0.5,@1];
    opencityAnimation.removedOnCompletion = YES;
    
    NSArray *animations = @[scaleAnimation,opencityAnimation];
    _animaTionGroup.animations = animations;
    [layer addAnimation:_animaTionGroup forKey:nil];
    
    [self performSelector:@selector(removeLayer:) withObject:layer afterDelay:1.5];
}

- (void)removeLayer:(CALayer *)layer
{
    [layer removeFromSuperlayer];
}


- (void)delayAnimation
{
    [self startAnimation];
}

完成以上操作,就大功告成了!

温馨提示:

1.由于是语音识别,需要用到麦克风相关权限,模拟器会爆12个错误,使用真机可以解决;

2.涉及到授权文件相关并不复杂,工程Bundle Identifier只需要设置百度的离线授权一致即可,如下图:


最终效果如下:



有不懂或不明白的地方可以微博联系我:


iOS开发者交流群:446310206





2016-09-26 09:16:38 LucioSui 阅读数 3939

在记录事件的时候,用户在不方便手写的时候,我们可以利用语音录入,转成文字的形式记录时间,是不是既方便又只能,现在做语音识别的有一些不错的开放平台供我们使用,科大讯飞平台,百度语音平台。科大讯飞的优势在于大段大段的文字识别上,准确率较高。这篇博客也主要讲的是是讯飞语音SDK的使用。下面我们详细看一下科大讯飞。

1.科大讯飞开放平台

http://www.xfyun.cn
2.科大讯飞iOS-API开放平台

第一步:申请账号ID
创建新应用(获得后续的appid以及开通服务)
登录到讯飞开放平台上,在用户菜单栏里创建应用,这里的登录也可以采用第三方方式,在创建应用的界面填写相关的信息即可,然后就会有一个SDK的下载链接,,如果没有直接去SDK选项下载即可。

第二步:导入讯飞SDK框架

下载下来SDK解压后有三个文件夹:doc文件夹:不用多说肯定是开发文档;重要的是接下来的那两个文件夹:一个是lib文件夹:存放科大讯飞SDK类库,这就是我们要导入的SDK;一个是sample的科大讯飞demo演示工程。

下面我们创建一个工程,将lib文件夹下的”iflyMSC.framework”拷贝到工程目录,然后在工程中添加依赖库,如下图所示:
这里写图片描述

第三步:开始进行语音识别

语音识别分两种,分别用在不同场合,一个是界面提示的语音识别,一个是无界面提示的语音识别,这里以有界面提示的语音识别为例先进性讲解。

3.1导入头文件

#import "iflyMSC/IFlyMSC.h"

这里写图片描述

#import "IFlyContact.h"
#import "IFlyDataUploader.h"
#import "IFlyDebugLog.h"
#import "IFlyISVDelegate.h"
#import "IFlyISVRecognizer.h"
#import "IFlyRecognizerView.h"
#import "IFlyRecognizerViewDelegate.h"
#import "IFlyResourceUtil.h"
#import "IFlySetting.h"
#import "IFlySpeechConstant.h"
#import "IFlySpeechError.h"
#import "IFlySpeechEvaluator.h"
#import "IFlySpeechEvaluatorDelegate.h"
#import "IFlySpeechEvent.h"
#import "IFlySpeechRecognizer.h"
#import "IFlySpeechRecognizerDelegate.h"
#import "IFlySpeechSynthesizer.h"
#import "IFlySpeechSynthesizerDelegate.h"
#import "IFlySpeechUnderstander.h"
#import "IFlySpeechUtility.h"
#import "IFlyTextUnderstander.h"
#import "IFlyUserWords.h"
#import "IFlyPcmRecorder.h"
#import "IFlySpeechEvaluator.h"
#import "IFlySpeechEvaluatorDelegate.h"

#import "IFlyVoiceWakeuper.h"
#import "IFlyVoiceWakeuperDelegate.h"

3.2登录讯飞服务器

在使用讯飞的语音解析之前,需要进行用户身份验证,即登录讯飞服务器,即讯飞服务器需要根据你当前用户的APPID才能同意你登录。代码如下:

//登陆语音平台
    NSString *initString = [[NSString alloc] initWithFormat:@"appid=%@",@"57e08eb8"];
    [IFlySpeechUtility createUtility:initString];

3.3创建有界面提示语音识别对象

//  Speech-JiKe
//
//  Created by rimi on 16/9/22.
//  Copyright © 2016年 LucioSui. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "iflyMSC/iflyMSC.h"
@class IFlySpeechRecognizer;

@interface ViewController : UIViewController<IFlySpeechRecognizerDelegate,IFlyRecognizerViewDelegate>

@property (nonatomic, strong) NSString *filePath;//音频文件路径

@property (nonatomic, strong) IFlySpeechRecognizer *iFlySpeechRecognizer;//不带界面的识别对象

@property (nonatomic, strong) IFlyRecognizerView *iflyRecognizerView;//带界面的识别对象

@property (nonatomic, strong) NSString * result;

@property (nonatomic, assign) BOOL isCanceled;

@end

3.4初始化带界面的识别对象

// 设置识别参数
-(void)initRecognizer
{
    NSLog(@"%s",__func__);

    if ([IATConfig sharedInstance].haveView == NO) {//无界面

        //单例模式,无UI的实例
        if (_iFlySpeechRecognizer == nil) {
            _iFlySpeechRecognizer = [IFlySpeechRecognizer sharedInstance];

            [_iFlySpeechRecognizer setParameter:@"" forKey:[IFlySpeechConstant PARAMS]];

            //设置听写模式
            [_iFlySpeechRecognizer setParameter:@"iat" forKey:[IFlySpeechConstant IFLY_DOMAIN]];
        }
        _iFlySpeechRecognizer.delegate = self;

        if (_iFlySpeechRecognizer != nil) {
            IATConfig *instance = [IATConfig sharedInstance];

            //设置最长录音时间
            [_iFlySpeechRecognizer setParameter:instance.speechTimeout forKey:[IFlySpeechConstant SPEECH_TIMEOUT]];
            //设置后端点
            [_iFlySpeechRecognizer setParameter:instance.vadEos forKey:[IFlySpeechConstant VAD_EOS]];
            //设置前端点
            [_iFlySpeechRecognizer setParameter:instance.vadBos forKey:[IFlySpeechConstant VAD_BOS]];
            //网络等待时间
            [_iFlySpeechRecognizer setParameter:@"20000" forKey:[IFlySpeechConstant NET_TIMEOUT]];

            //设置采样率,推荐使用16K
            [_iFlySpeechRecognizer setParameter:instance.sampleRate forKey:[IFlySpeechConstant SAMPLE_RATE]];

            if ([instance.language isEqualToString:[IATConfig chinese]]) {
                //设置语言
                [_iFlySpeechRecognizer setParameter:instance.language forKey:[IFlySpeechConstant LANGUAGE]];
                //设置方言
                [_iFlySpeechRecognizer setParameter:instance.accent forKey:[IFlySpeechConstant ACCENT]];
            }else if ([instance.language isEqualToString:[IATConfig english]]) {
                [_iFlySpeechRecognizer setParameter:instance.language forKey:[IFlySpeechConstant LANGUAGE]];
            }
            //设置是否返回标点符号
            [_iFlySpeechRecognizer setParameter:instance.dot forKey:[IFlySpeechConstant ASR_PTT]];

        }
    }else  {//有界面

        //单例模式,UI的实例
        if (_iflyRecognizerView == nil) {
            //UI显示剧中
            _iflyRecognizerView= [[IFlyRecognizerView alloc] initWithCenter:self.view.center];

            [_iflyRecognizerView setParameter:@"" forKey:[IFlySpeechConstant PARAMS]];

            //设置听写模式
            [_iflyRecognizerView setParameter:@"iat" forKey:[IFlySpeechConstant IFLY_DOMAIN]];

        }
        _iflyRecognizerView.delegate = self;

        if (_iflyRecognizerView != nil) {
            IATConfig *instance = [IATConfig sharedInstance];
            //设置最长录音时间
            [_iflyRecognizerView setParameter:instance.speechTimeout forKey:[IFlySpeechConstant SPEECH_TIMEOUT]];
            //设置后端点
            [_iflyRecognizerView setParameter:instance.vadEos forKey:[IFlySpeechConstant VAD_EOS]];
            //设置前端点
            [_iflyRecognizerView setParameter:instance.vadBos forKey:[IFlySpeechConstant VAD_BOS]];
            //网络等待时间
            [_iflyRecognizerView setParameter:@"20000" forKey:[IFlySpeechConstant NET_TIMEOUT]];

            //设置采样率,推荐使用16K
            [_iflyRecognizerView setParameter:instance.sampleRate forKey:[IFlySpeechConstant SAMPLE_RATE]];
            if ([instance.language isEqualToString:[IATConfig chinese]]) {
                //设置语言
                [_iflyRecognizerView setParameter:instance.language forKey:[IFlySpeechConstant LANGUAGE]];
                //设置方言
                [_iflyRecognizerView setParameter:instance.accent forKey:[IFlySpeechConstant ACCENT]];
            }else if ([instance.language isEqualToString:[IATConfig english]]) {
                //设置语言
                [_iflyRecognizerView setParameter:instance.language forKey:[IFlySpeechConstant LANGUAGE]];
            }
            //设置是否返回标点符号
            [_iflyRecognizerView setParameter:instance.dot forKey:[IFlySpeechConstant ASR_PTT]];

        }
    }
}

3.5实现代理方法

#pragma mark 错误的回调函数
- (void) onError:(IFlySpeechError *) error
{
    NSLog(@"%s",__func__);

    if ([IATConfig sharedInstance].haveView == NO ) {
        NSString *text ;

        if (self.isCanceled) {
            text = @"识别取消";

        } else if (error.errorCode == 0 ) {
            if (_result.length == 0) {
                text = @"无识别结果";
            }else {
                text = @"识别成功";
            }
        }else {
            text = [NSString stringWithFormat:@"发生错误:%d %@", error.errorCode,error.errorDesc];
            NSLog(@"%@",text);
        }

    }else {

        NSLog(@"errorCode:%d",[error errorCode]);
    }
}


//无界面,听写结果回调
// results:听写结果
// isLast:表示最后一次

- (void)onResults:(NSArray *) results isLast:(BOOL)isLast
{

    _volumLabel.alpha = 0.0;

    NSMutableString *resultString = [[NSMutableString alloc] init];
    NSDictionary *dic = results[0];
    for (NSString *key in dic) {
        [resultString appendFormat:@"%@",key];
    }
    _result =[NSString stringWithFormat:@"%@",resultString];
    NSString * resultFromJson =  [ISRDataHelper stringFromJson:resultString];
    _textLabel.text = [NSString stringWithFormat:@"%@%@",_textLabel.text,resultFromJson];

    if (isLast){
        NSLog(@"听写结果(json):%@测试", self.result);
    }
    NSLog(@"_result=%@",_result);

}

// 有界面,听写结果回调
// resultArray:听写结果
// isLast:表示最后一次

- (void)onResult:(NSArray *)resultArray isLast:(BOOL)isLast
{
    _volumLabel.alpha = 0.0;

    NSMutableString *result = [[NSMutableString alloc] init];
    NSDictionary *dic = [resultArray objectAtIndex:0];

    for (NSString *key in dic) {
        [result appendFormat:@"%@",key];
    }
    _textLabel.text = [NSString stringWithFormat:@"%@",result];
}

3.6开始识别语音

音频文件保存地址

 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachePath = [paths objectAtIndex:0];
    _filePath = [[NSString alloc] initWithFormat:@"%@",[cachePath stringByAppendingPathComponent:@"asr.pcm"]];

只提取了一部分代码,持续更新,后期上DEMO。

没有更多推荐了,返回首页