科大讯飞无限制录音

            最近想要做一款语音听写APP,在网上搜索关于如何使用科大讯飞语音的Demo少之又少,又或者是只是单纯的按照文档来实现简单的语音听写,远远不能满足需求,看了几天的文档和自己搜索的一些资料,还有这几天中遇到的一些问题,觉得有必要做一个笔记,能给初学者一些帮助,也顺便理一下这些天的一些收获,本人只是一个初学者,假如有写得不对或者不好的地方,还望大家指出~~

1、首先当然是创建应用,我这里只是使用了语音听写的功能,创建完成后下载SDK,打开是这样子的


2、导入SDK:

将开发工具包中libs目录下的Msc.jar和Sunflower.jar复制到Android工程的libs目录中,将online文件夹里面的子文件粘贴到工程目录src/main/jniLibs(这是Android studio 和eclipse的不同之处)。假如你要使用它自带的UI动画对话框录音,请将assets文件夹以及里面的子文件粘贴到工程目录src/main/下面,完成后如下图:




3、添加权限:

 
   
  1. <!–连接网络权限,用于执行云端语音能力 –>  
  2. <uses-permission android:name=”android.permission.INTERNET”></uses-permission>  
  3. <!–获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 –>  
  4. <uses-permission android:name=”android.permission.RECORD_AUDIO”></uses-permission>  
  5. <!–读取网络信息状态 –>  
  6. <uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”></uses-permission>  
  7. <!–获取当前wifi状态 –>  
  8. <uses-permission android:name=”android.permission.ACCESS_WIFI_STATE”></uses-permission>  
  9. <!–允许程序改变网络连接状态 –>  
  10. <uses-permission android:name=”android.permission.CHANGE_NETWORK_STATE”></uses-permission>  
  11. <!–读取手机信息权限 –>  
  12. <uses-permission android:name=”android.permission.READ_PHONE_STATE”></uses-permission>  
  13. <!–读取联系人权限,上传联系人需要用到此权限 –>  
  14. <uses-permission android:name=”android.permission.READ_CONTACTS”></uses-permission>  
  15. <!–假如我们要保存录音,还需要以下权限–>  
  16. <!– 在SDCard中创建与删除文件权限 –>  
  17. <uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”></uses-permission>  
  18. <!– SD卡权限  –>  
  19. <uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”></uses-permission>  
  20. <!– 允许程序读取或写入系统设置 –>  
  21. <uses-permission android:name=”android.permission.WRITE_SETTINGS”></uses-permission>  
<!--连接网络权限,用于执行云端语音能力 -->
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
<!--读取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<!--获取当前wifi状态 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<!--允许程序改变网络连接状态 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"></uses-permission>
<!--读取手机信息权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<!--读取联系人权限,上传联系人需要用到此权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<!--假如我们要保存录音,还需要以下权限-->
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
<!-- SD卡权限  -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<!-- 允许程序读取或写入系统设置 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"></uses-permission>



4、初始化:

//将“12345678”替换成您申请的APPID,申请地址:http://open.voicecloud.cn

SpeechUtility.createUtility(this, "appid=123456789");
 
   
数据收集接口:
 
   
 
   
 
  1. @Override  
  2. protected void onResume() {  
  3.         // 开放统计 移动数据统计分析  
  4.         FlowerCollector.onResume(MainActivity.this);  
  5.         super.onResume();  
  6. }  
  7.   
  8. @Override  
  9. protected void onPause() {  
  10.         // 开放统计 移动数据统计分析  
  11.         FlowerCollector.onPause(MainActivity.this);  
  12.         super.onPause();  
  13. }  
@Override
protected void onResume() {
        // 开放统计 移动数据统计分析
        FlowerCollector.onResume(MainActivity.this);
        super.onResume();
}

@Override
protected void onPause() {
        // 开放统计 移动数据统计分析
        FlowerCollector.onPause(MainActivity.this);
        super.onPause();
}

说明:
1.确保在所有的 activity 中都调用 FlowerCollector.onResume() FlowerCollector.onPause()方法。这两个调用将不会阻塞应用程序的主线程,也不会影响应用程序的性能。
2.注意,如果您的 Activity 之间有继承或者控制关系请不要同时在父和子 Activity 中重复添加onPause onResume 方法,否则会造成重复统计(eg:使用 TabHostTabActivityActivityGroup )3.一个应用程序在多个 activity 之间连续切换时,会被视为同一个 session(启动)4.当用户两次使用之间间隔超过 30 秒时,将被认为是两个的独立的 session(启动)。例如:用户回到home,或进入其他程序,经过一段时间后再返回之前的应用。
5.所有日志收集工作均在 onResume 之后进行,在 onPause 之后结束。 (ps:其实我也不懂这是干嘛的,猜想是为了收集数据的,还有一些参数设置我就不罗列了,有兴趣的同学可以去看,因为我发现就算没设置它们我也能进行语音听写,反正收集的数据我看不懂)


5、语音听写的使用(采取云端听写的引擎模式,暂时未考虑本地集成)

注意导包:com.iflytek.cloud而不是android.speech.SpeechRecognizer

1)使用自带UI语音对话框

优点:简单方便、美观

缺点:端点超时会自动停止识别(不理解的往下看设置参数说明)默认前段点:5000 后端点:1800

这是使用语音识别最简单的方法,在点击事件上调用start()方法就好了

  1. public void start() {  
  2.         //1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener  
  3.         iatDialog = new RecognizerDialog(this, initListener);  
  4.         //2.设置听写参数  
  5.         iatDialog.setParameter(SpeechConstant.DOMAIN, “iat”);  
  6.         iatDialog.setParameter(SpeechConstant.LANGUAGE, “zh_cn”);  
  7.         iatDialog.setParameter(SpeechConstant.ACCENT, “mandarin ”);  
  8.         //3.设置回调接口  
  9.         iatDialog.setListener(new RecognizerDialogListener() {  
  10.             @Override  
  11.             public void onResult(RecognizerResult recognizerResult, boolean b) {  
  12.                 if (!b) {  
  13.                     String json = recognizerResult.getResultString();  
  14.                     String str = JsonParser.parseIatResult(json);  
  15.                     System.out.println(“说话内容:”+str);  
  16.                     textView.setText(str);  
  17.                 }  
  18.             }  
  19.   
  20.             @Override  
  21.             public void onError(SpeechError speechError) {  
  22.                 Log.d(“error”, speechError.toString());  
  23.             }  
  24.         });  
  25. //4.开始听写  
  26.         iatDialog.show();  
  27.     }  
public void start() {
        //1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener
        iatDialog = new RecognizerDialog(this, initListener);
        //2.设置听写参数
        iatDialog.setParameter(SpeechConstant.DOMAIN, "iat");
        iatDialog.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        iatDialog.setParameter(SpeechConstant.ACCENT, "mandarin ");
        //3.设置回调接口
        iatDialog.setListener(new RecognizerDialogListener() {
            @Override
            public void onResult(RecognizerResult recognizerResult, boolean b) {
                if (!b) {
                    String json = recognizerResult.getResultString();
                    String str = JsonParser.parseIatResult(json);
                    System.out.println("说话内容:"+str);
                    textView.setText(str);
                }
            }

            @Override
            public void onError(SpeechError speechError) {
                Log.d("error", speechError.toString());
            }
        });
//4.开始听写
        iatDialog.show();
    }

增加if(!b)的判断是因为每次说话结束之后,返回数据的最后一次是返回一个标点符号”。”或者”!”之类的,当然你也可以设置不返回标点符号。

  1. //设置是否带标点符号 0表示不带标点,1则表示带标点。  
  2. mIat.setParameter(SpeechConstant.ASR_PTT, ”0”);  
//设置是否带标点符号 0表示不带标点,1则表示带标点。
mIat.setParameter(SpeechConstant.ASR_PTT, "0");

2)不使用自带UI对话框

优点:可以设置自己想要的参数

缺点:使用比较麻烦,对于初学者来讲,根据讲话音量大小自定义麦克风效果的View简直是噩梦(我不会,哪位大神做出来了求分享~~)

简单的用法:

1.创建对象:

  1. <span style=“font-size:18px;”>//1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener  
  2. mIat = SpeechRecognizer.createRecognizer(thisnull);</span>  
<span style="font-size:18px;">//1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener
mIat = SpeechRecognizer.createRecognizer(this, null);</span>

  2.设置参数(听写这三个参数是必须的,下面设置时不再提示):
  1. mIat.setParameter(SpeechConstant.DOMAIN, “iat”);  
  2. // 简体中文:”zh_cn”, 美式英文:”en_us”  
  3. mIat.setParameter(SpeechConstant.LANGUAGE, ”zh_cn”);  
  4. //普通话:mandarin(默认)  
  5. //粤 语:cantonese  
  6. //四川话:lmz  
  7. //河南话:henanese  
  8. mIat.setParameter(SpeechConstant.ACCENT, ”mandarin ”);  
mIat.setParameter(SpeechConstant.DOMAIN, "iat");
// 简体中文:"zh_cn", 美式英文:"en_us"
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//普通话:mandarin(默认)
//粤 语:cantonese
//四川话:lmz
//河南话:henanese
mIat.setParameter(SpeechConstant.ACCENT, "mandarin ");
3.实例化监听对象
   
  1. private RecognizerListener recognizerListener = new RecognizerListener() {  
  2.         @Override  
  3.         public void onVolumeChanged(int i, byte[] bytes) {  
  4.   
  5.         }  
  6.   
  7.         @Override  
  8.         public void onBeginOfSpeech() {  
  9.             System.out.println(”开始识别”);  
  10.         }  
  11.   
  12.         @Override  
  13.         public void onEndOfSpeech() {  
  14.             System.out.println(”识别结束”);  
  15.         }  
  16.   
  17.         @Override  
  18.         public void onResult(RecognizerResult recognizerResult, boolean b) {  
  19.             String str=JsonParser.parseIatResult(recognizerResult.getResultString());  
  20.             System.out.println(”识别结果”+str);  
  21.         }  
  22.   
  23.         @Override  
  24.         public void onError(SpeechError speechError) {  
  25.             System.out.println(”识别出错”);  
  26.         }  
  27.   
  28.         @Override  
  29.         public void onEvent(int i, int i1, int i2, Bundle bundle) {  
  30.               
  31.         }  
  32.     };  
private RecognizerListener recognizerListener = new RecognizerListener() {
        @Override
        public void onVolumeChanged(int i, byte[] bytes) {

        }

        @Override
        public void onBeginOfSpeech() {
            System.out.println("开始识别");
        }

        @Override
        public void onEndOfSpeech() {
            System.out.println("识别结束");
        }

        @Override
        public void onResult(RecognizerResult recognizerResult, boolean b) {
            String str=JsonParser.parseIatResult(recognizerResult.getResultString());
            System.out.println("识别结果"+str);
        }

        @Override
        public void onError(SpeechError speechError) {
            System.out.println("识别出错");
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    };
4.添加监听
 
   
  1. mIat.startListening(recognizerListener);  
mIat.startListening(recognizerListener);

官方Demo给出的解析Json的类:
 
   
  1. package com.hxl.voicetest1;  
  2.   
  3. import org.json.JSONArray;  
  4. import org.json.JSONObject;  
  5. import org.json.JSONTokener;  
  6.   
  7. /** 
  8.  * Json结果解析类 
  9.  */  
  10. public class JsonParser {  
  11.   
  12.     public static String parseIatResult(String json) {  
  13.         StringBuffer ret = new StringBuffer();  
  14.         try {  
  15.             JSONTokener tokener = new JSONTokener(json);  
  16.             JSONObject joResult = new JSONObject(tokener);  
  17.   
  18.             JSONArray words = joResult.getJSONArray(”ws”);  
  19.             for (int i = 0; i < words.length(); i++) {  
  20.                 // 转写结果词,默认使用第一个结果  
  21.                 JSONArray items = words.getJSONObject(i).getJSONArray(”cw”);  
  22.                 JSONObject obj = items.getJSONObject(0);  
  23.                 ret.append(obj.getString(”w”));  
  24.                 //如果需要多候选结果,解析数组其他字段  
  25.                 //for(int j = 0; j < items.length(); j++)  
  26.                 //{  
  27.                 //JSONObject obj = items.getJSONObject(j);  
  28.                 //ret.append(obj.getString(“w”));  
  29.                 //}  
  30.             }  
  31.         } catch (Exception e) {  
  32.             e.printStackTrace();  
  33.         }  
  34.         return ret.toString();  
  35.     }  
  36.   
  37.     public static String parseGrammarResult(String json) {  
  38.         StringBuffer ret = new StringBuffer();  
  39.         try {  
  40.             JSONTokener tokener = new JSONTokener(json);  
  41.             JSONObject joResult = new JSONObject(tokener);  
  42.   
  43.             JSONArray words = joResult.getJSONArray(”ws”);  
  44.             for (int i = 0; i < words.length(); i++) {  
  45.                 JSONArray items = words.getJSONObject(i).getJSONArray(”cw”);  
  46.                 for (int j = 0; j < items.length(); j++) {  
  47.                     JSONObject obj = items.getJSONObject(j);  
  48.                     if (obj.getString(“w”).contains(“nomatch”)) {  
  49.                         ret.append(”没有匹配结果.”);  
  50.                         return ret.toString();  
  51.                     }  
  52.                     ret.append(”【结果】” + obj.getString(“w”));  
  53.                     ret.append(”【置信度】” + obj.getInt(“sc”));  
  54.                     ret.append(”n”);  
  55.                 }  
  56.             }  
  57.         } catch (Exception e) {  
  58.             e.printStackTrace();  
  59.             ret.append(”没有匹配结果.”);  
  60.         }  
  61.         return ret.toString();  
  62.     }  
  63. }  
package com.hxl.voicetest1;

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

/**
 * Json结果解析类
 */
public class JsonParser {

    public static String parseIatResult(String json) {
        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"));
                //如果需要多候选结果,解析数组其他字段
                //for(int j = 0; j < items.length(); j++)
                //{
                //JSONObject obj = items.getJSONObject(j);
                //ret.append(obj.getString("w"));
                //}
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret.toString();
    }

    public static String parseGrammarResult(String json) {
        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");
                for (int j = 0; j < items.length(); j++) {
                    JSONObject obj = items.getJSONObject(j);
                    if (obj.getString("w").contains("nomatch")) {
                        ret.append("没有匹配结果.");
                        return ret.toString();
                    }
                    ret.append("【结果】" + obj.getString("w"));
                    ret.append("【置信度】" + obj.getInt("sc"));
                    ret.append("n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        }
        return ret.toString();
    }
}

到这里,科大讯飞语音最简单的听写Demo就算完成了。这时你们会想:WTF?就这些我还不如自个儿看文档…..
 
   
当然了,既然写这篇博客,肯定不仅仅是简单介绍科大讯飞语音听写的使用而已。
 
   
我想要的效果是:无限制时间录音,并且我想知道我在第几秒说了什么话,回放录音的时候,播放到哪就显示相应的文字,还可以对识别错误的字段进行纠错修改。
 
   
我们来看一下一些常用的设置参数(个人认为),我们可以根据我们的需求来设置相应的参数
 
   
  1. // 清空参数  
  2. mIat.setParameter(SpeechConstant.PARAMS, null);  
  3. //短信和日常用语:iat (默认)  视频:video  地图:poi  音乐:music  
  4. mIat.setParameter(SpeechConstant.DOMAIN, ”iat”);  
  5. // 简体中文:”zh_cn”, 美式英文:”en_us”  
  6. mIat.setParameter(SpeechConstant.LANGUAGE, ”zh_cn”);  
  7. //普通话:mandarin(默认)  
  8. //粤 语:cantonese  
  9. //四川话:lmz  
  10. //河南话:henanese<span style=”font-family: Menlo;”>     </span>  
  11. mIat.setParameter(SpeechConstant.ACCENT, ”mandarin ”);  
  12. // 设置听写引擎 “cloud”, “local”,”mixed”  在线  本地  混合  
  13. //本地的需要本地功能集成  
  14. mIat.setParameter(SpeechConstant.ENGINE_TYPE, ”cloud”);  
  15. // 设置返回结果格式 听写会话支持json和plain  
  16. mIat.setParameter(SpeechConstant.RESULT_TYPE, ”json”);  
  17. //设置是否带标点符号 0表示不带标点,1则表示带标点。  
  18. mIat.setParameter(SpeechConstant.ASR_PTT, ”0”);  
  19. //只有设置这个属性为1时,VAD_BOS  VAD_EOS才会生效,且RecognizerListener.onVolumeChanged才有音量返回默认:1  
  20. mIat.setParameter(SpeechConstant.VAD_ENABLE,”1”);  
  21. // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理1000~10000  
  22. mIat.setParameter(SpeechConstant.VAD_BOS, ”5000”);  
  23. // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音0~10000  
  24. mIat.setParameter(SpeechConstant.VAD_EOS, ”1800”);  
  25. // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限  
  26. // 注:AUDIO_FORMAT参数语记需要更新版本才能生效  
  27. mIat.setParameter(SpeechConstant.AUDIO_FORMAT, ”wav”);  
  28. //设置识别会话被中断时(如当前会话未结束就开启了新会话等),  
  29. //是否通 过RecognizerListener.onError(com.iflytek.cloud.SpeechError)回调ErrorCode.ERROR_INTERRUPT错误。  
  30. //默认false    [null,true,false]  
  31. mIat.setParameter(SpeechConstant.ASR_INTERRUPT_ERROR,”false”);  
  32. //音频采样率  8000~16000  默认:16000  
  33. mIat.setParameter(SpeechConstant.SAMPLE_RATE,”16000”);  
  34. //默认:麦克风(1)(MediaRecorder.AudioSource.MIC)  
  35. //在写音频流方式(-1)下,应用层通过writeAudio函数送入音频;  
  36. //在传文件路径方式(-2)下,SDK通过应用层设置的ASR_SOURCE_PATH值, 直接读取音频文件。目前仅在SpeechRecognizer中支持。  
  37. mIat.setParameter(SpeechConstant.AUDIO_SOURCE, ”-1”);  
  38. //保存音频文件的路径   仅支持pcm和wav  
  39. mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + ”test.wav”);  
        // 清空参数
        mIat.setParameter(SpeechConstant.PARAMS, null);
        //短信和日常用语:iat (默认)  视频:video  地图:poi  音乐:music
        mIat.setParameter(SpeechConstant.DOMAIN, "iat");
        // 简体中文:"zh_cn", 美式英文:"en_us"
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        //普通话:mandarin(默认)
        //粤 语:cantonese
        //四川话:lmz
        //河南话:henanese<span style="font-family: Menlo;">     </span>
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin ");
        // 设置听写引擎 "cloud", "local","mixed"  在线  本地  混合
        //本地的需要本地功能集成
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, "cloud");
        // 设置返回结果格式 听写会话支持json和plain
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
        //设置是否带标点符号 0表示不带标点,1则表示带标点。
        mIat.setParameter(SpeechConstant.ASR_PTT, "0");
        //只有设置这个属性为1时,VAD_BOS  VAD_EOS才会生效,且RecognizerListener.onVolumeChanged才有音量返回默认:1
        mIat.setParameter(SpeechConstant.VAD_ENABLE,"1");
        // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理1000~10000
        mIat.setParameter(SpeechConstant.VAD_BOS, "5000");
        // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音0~10000
        mIat.setParameter(SpeechConstant.VAD_EOS, "1800");
        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        //设置识别会话被中断时(如当前会话未结束就开启了新会话等),
        //是否通 过RecognizerListener.onError(com.iflytek.cloud.SpeechError)回调ErrorCode.ERROR_INTERRUPT错误。
        //默认false    [null,true,false]
        mIat.setParameter(SpeechConstant.ASR_INTERRUPT_ERROR,"false");
        //音频采样率  8000~16000  默认:16000
        mIat.setParameter(SpeechConstant.SAMPLE_RATE,"16000");
        //默认:麦克风(1)(MediaRecorder.AudioSource.MIC)
        //在写音频流方式(-1)下,应用层通过writeAudio函数送入音频;
        //在传文件路径方式(-2)下,SDK通过应用层设置的ASR_SOURCE_PATH值, 直接读取音频文件。目前仅在SpeechRecognizer中支持。
        mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
        //保存音频文件的路径   仅支持pcm和wav
        mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "test.wav");


我首先想到当然就是设置音频保存路径了啊,这还不简单
 
   
 
   
  1. mIat.setParameter(SpeechConstant.AUDIO_FORMAT, “wav”);  
  2. mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要保存的路径”);  
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, "要保存的路径");

 
   
时间戳这个好办,根据录音开始时记录当前时间startTime,在RecognizerListener的onResult方法获取当前时间currentTime,然后用currentTime-startTime 就是第几秒说的话了(由于云端识别会有延迟,这个秒数其实是不正确的,这里先忽略这个问题)。

结束了?
不是,我们再来看一下科大讯飞的说明文档中
 
   
 
   
 
   
科大讯飞对语音听写做了限制,端点超时最大也只能设置10秒,超过这个时间识别自动终止,不再对后续的语音部分进行识别。假如我们要长时间录音,不可能让用户每10秒钟就要说一句话,而且还有一个是值得我们注意的:
 
   
 
   
 
   
也就是说,就算我们连续不停的讲话,音频录制最多也就是60秒而已,怎么办?
这时候我就想,我用自己的方法录音,用科大讯飞的去识别,这是个好方法,我立马在百度输入框敲上 ”Android录音
这里推荐一个大神封装的录音类:http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html
 
   
 
   
考虑到科大讯飞只支持wav、pcm文件,我只是把AudioRecordFunc类copy过来,考虑到科大讯飞对音频文件识别的要求:

上传音频的采样率与采样精度:A:采样率16KHZ或者8KHZ,单声道,采样精度16bit的PCM或者WAV格式的音频

我将代码进行了部分修改。
 
   
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6.   
  7. import android.media.AudioFormat;  
  8. import android.media.AudioRecord;  
  9.   
  10. public class AudioRecordFunc {  
  11.     // 缓冲区字节大小    
  12.     private int bufferSizeInBytes = 0;  
  13.   
  14.     //AudioName裸音频数据文件 ,麦克风  
  15.     private String AudioName = “”;  
  16.   
  17.     //NewAudioName可播放的音频文件    
  18.     private String NewAudioName = “”;  
  19.   
  20.     private AudioRecord audioRecord;  
  21.     private boolean isRecord = false;// 设置正在录制的状态    
  22.   
  23.   
  24.     private static AudioRecordFunc mInstance;  
  25.   
  26.     private AudioRecordFunc() {  
  27.   
  28.     }  
  29.   
  30.     public synchronized static AudioRecordFunc getInstance() {  
  31.         if (mInstance == null)  
  32.             mInstance = new AudioRecordFunc();  
  33.         return mInstance;  
  34.     }  
  35.   
  36.     public int startRecordAndFile() {  
  37.         //判断是否有外部存储设备sdcard  
  38.         if (AudioFileFunc.isSdcardExit()) {  
  39.             if (isRecord) {  
  40.                 return ErrorCode.E_STATE_RECODING;  
  41.             } else {  
  42.                 if (audioRecord == null)  
  43.                     creatAudioRecord();  
  44.   
  45.                 audioRecord.startRecording();  
  46.                 // 让录制状态为true    
  47.                 isRecord = true;  
  48.                 // 开启音频文件写入线程    
  49.                 new Thread(new AudioRecordThread()).start();  
  50.   
  51.                 return ErrorCode.SUCCESS;  
  52.             }  
  53.   
  54.         } else {  
  55.             return ErrorCode.E_NOSDCARD;  
  56.         }  
  57.   
  58.     }  
  59.   
  60.     public void stopRecordAndFile() {  
  61.         close();  
  62.     }  
  63.   
  64.   
  65.     public long getRecordFileSize() {  
  66.         return AudioFileFunc.getFileSize(NewAudioName);  
  67.     }  
  68.   
  69.   
  70.     private void close() {  
  71.         if (audioRecord != null) {  
  72.             System.out.println(”stopRecord”);  
  73.             isRecord = false;//停止文件写入    
  74.             audioRecord.stop();  
  75.             audioRecord.release();//释放资源    
  76.             audioRecord = null;  
  77.         }  
  78.     }  
  79.   
  80.   
  81.     private void creatAudioRecord() {  
  82.         // 获取音频文件路径  
  83.         AudioName = AudioFileFunc.getRawFilePath();  
  84.         NewAudioName = AudioFileFunc.getWavFilePath();  
  85.   
  86.         // 获得缓冲区字节大小    
  87.         bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE,  
  88.                 AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);  
  89.   
  90.   
  91.         // 创建AudioRecord对象(修改处)  
  92.         audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_ IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);  
  93.     }  
  94.   
  95.     class AudioRecordThread implements Runnable {  
  96.         @Override  
  97.         public void run() {  
  98.             writeDateTOFile();//往文件中写入裸数据  
  99.             copyWaveFile(AudioName, NewAudioName);//给裸数据加上头文件  
  100.         }  
  101.     }  
  102.   
  103.     /** 
  104.      * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频 
  105.      * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理 
  106.      * 比如你要做一个爱说话的TOM猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。 
  107.      */  
  108.     private void writeDateTOFile() {  
  109.         // new一个byte数组用来存一些字节数据,大小为缓冲区大小  
  110.         byte[] audiodata = new byte[bufferSizeInBytes];  
  111.         FileOutputStream fos = null;  
  112.         int readsize = 0;  
  113.         try {  
  114.             File file = new File(AudioName);  
  115.             if (file.exists()) {  
  116.                 file.delete();  
  117.             }  
  118.             fos = new FileOutputStream(file);  
  119.             // 建立一个可存取字节的文件  
  120.         } catch (Exception e) {  
  121.             e.printStackTrace();  
  122.         }  
  123.         while (isRecord == true) {  
  124.             readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);  
  125.             if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {  
  126.                 try {  
  127.                     fos.write(audiodata);  
  128.                 } catch (IOException e) {  
  129.                     e.printStackTrace();  
  130.                 }  
  131.             }  
  132.         }  
  133.         try {  
  134.             if (fos != null)  
  135.                 fos.close();// 关闭写入流  
  136.         } catch (IOException e) {  
  137.             e.printStackTrace();  
  138.         }  
  139.     }  
  140.   
  141.     // 这里得到可播放的音频文件  
  142.     private void copyWaveFile(String inFilename, String outFilename) {  
  143.         FileInputStream in = null;  
  144.         FileOutputStream out = null;  
  145.         long totalAudioLen = 0;  
  146.         long totalDataLen = totalAudioLen + 36;  
  147.         long longSampleRate = AudioFileFunc.AUDIO_SAMPLE_RATE;  
  148.         int channels = 2;  
  149.         long byteRate = 16 * AudioFileFunc.AUDIO_SAMPLE_RATE * channels / 8;  
  150.         byte[] data = new byte[bufferSizeInBytes];  
  151.         try {  
  152.             in = new FileInputStream(inFilename);  
  153.             out = new FileOutputStream(outFilename);  
  154.             totalAudioLen = in.getChannel().size();  
  155.             totalDataLen = totalAudioLen + 36;  
  156.             WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);  
  157.             while (in.read(data) != -1) {  
  158.                 out.write(data);  
  159.             }  
  160.             in.close();  
  161.             out.close();  
  162.         } catch (FileNotFoundException e) {  
  163.             e.printStackTrace();  
  164.         } catch (IOException e) {  
  165.             e.printStackTrace();  
  166.         }  
  167.     }  
  168.   
  169.     /** 
  170.      * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。       
  171.      * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav 
  172.      * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有自己特有的头文件。 
  173.      */  
  174.     private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {  
  175.         byte[] header = new byte[44];  
  176.         header[0] = ‘R’// RIFF/WAVE header  
  177.         header[1] = ‘I’;  
  178.         header[2] = ‘F’;  
  179.         header[3] = ‘F’;  
  180.         header[4] = (byte) (totalDataLen & 0xff);  
  181.         header[5] = (byte) ((totalDataLen >> 8) & 0xff);  
  182.         header[6] = (byte) ((totalDataLen >> 16) & 0xff);  
  183.         header[7] = (byte) ((totalDataLen >> 24) & 0xff);  
  184.         header[8] = ‘W’;  
  185.         header[9] = ‘A’;  
  186.         header[10] = ‘V’;  
  187.         header[11] = ‘E’;  
  188.         header[12] = ‘f’// ‘fmt ’ chunk  
  189.         header[13] = ‘m’;  
  190.         header[14] = ‘t’;  
  191.         header[15] = ‘ ’;  
  192.         header[16] = 16// 4 bytes: size of ‘fmt ’ chunk  
  193.         header[17] = 0;  
  194.         header[18] = 0;  
  195.         header[19] = 0;  
  196.         header[20] = 1// format = 1  
  197.         header[21] = 0;  
  198.         header[22] = (byte) channels;  
  199.         header[23] = 0;  
  200.         header[24] = (byte) (longSampleRate & 0xff);  
  201.         header[25] = (byte) ((longSampleRate >> 8) & 0xff);  
  202.         header[26] = (byte) ((longSampleRate >> 16) & 0xff);  
  203.         header[27] = (byte) ((longSampleRate >> 24) & 0xff);  
  204.         header[28] = (byte) (byteRate & 0xff);  
  205.         header[29] = (byte) ((byteRate >> 8) & 0xff);  
  206.         header[30] = (byte) ((byteRate >> 16) & 0xff);  
  207.         header[31] = (byte) ((byteRate >> 24) & 0xff);  
  208.         header[32] = (byte) (2 * 16 / 8); // block align  
  209.         header[33] = 0;  
  210.         header[34] = 16// bits per sample  
  211.         header[35] = 0;  
  212.         header[36] = ‘d’;  
  213.         header[37] = ‘a’;  
  214.         header[38] = ‘t’;  
  215.         header[39] = ‘a’;  
  216.         header[40] = (byte) (totalAudioLen & 0xff);  
  217.         header[41] = (byte) ((totalAudioLen >> 8) & 0xff);  
  218.         header[42] = (byte) ((totalAudioLen >> 16) & 0xff);  
  219.         header[43] = (byte) ((totalAudioLen >> 24) & 0xff);  
  220.         out.write(header, 044);  
  221.     }  
  222. }  
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.media.AudioFormat;
import android.media.AudioRecord;

public class AudioRecordFunc {
    // 缓冲区字节大小  
    private int bufferSizeInBytes = 0;

    //AudioName裸音频数据文件 ,麦克风
    private String AudioName = "";

    //NewAudioName可播放的音频文件  
    private String NewAudioName = "";

    private AudioRecord audioRecord;
    private boolean isRecord = false;// 设置正在录制的状态  


    private static AudioRecordFunc mInstance;

    private AudioRecordFunc() {

    }

    public synchronized static AudioRecordFunc getInstance() {
        if (mInstance == null)
            mInstance = new AudioRecordFunc();
        return mInstance;
    }

    public int startRecordAndFile() {
        //判断是否有外部存储设备sdcard
        if (AudioFileFunc.isSdcardExit()) {
            if (isRecord) {
                return ErrorCode.E_STATE_RECODING;
            } else {
                if (audioRecord == null)
                    creatAudioRecord();

                audioRecord.startRecording();
                // 让录制状态为true  
                isRecord = true;
                // 开启音频文件写入线程  
                new Thread(new AudioRecordThread()).start();

                return ErrorCode.SUCCESS;
            }

        } else {
            return ErrorCode.E_NOSDCARD;
        }

    }

    public void stopRecordAndFile() {
        close();
    }


    public long getRecordFileSize() {
        return AudioFileFunc.getFileSize(NewAudioName);
    }


    private void close() {
        if (audioRecord != null) {
            System.out.println("stopRecord");
            isRecord = false;//停止文件写入  
            audioRecord.stop();
            audioRecord.release();//释放资源  
            audioRecord = null;
        }
    }


    private void creatAudioRecord() {
        // 获取音频文件路径
        AudioName = AudioFileFunc.getRawFilePath();
        NewAudioName = AudioFileFunc.getWavFilePath();

        // 获得缓冲区字节大小  
        bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);


        // 创建AudioRecord对象(修改处)
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_ IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
    }

    class AudioRecordThread implements Runnable {
        @Override
        public void run() {
            writeDateTOFile();//往文件中写入裸数据
            copyWaveFile(AudioName, NewAudioName);//给裸数据加上头文件
        }
    }

    /**
     * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频
     * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理
     * 比如你要做一个爱说话的TOM猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。
     */
    private void writeDateTOFile() {
        // new一个byte数组用来存一些字节数据,大小为缓冲区大小
        byte[] audiodata = new byte[bufferSizeInBytes];
        FileOutputStream fos = null;
        int readsize = 0;
        try {
            File file = new File(AudioName);
            if (file.exists()) {
                file.delete();
            }
            fos = new FileOutputStream(file);
            // 建立一个可存取字节的文件
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (isRecord == true) {
            readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
                try {
                    fos.write(audiodata);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        try {
            if (fos != null)
                fos.close();// 关闭写入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 这里得到可播放的音频文件
    private void copyWaveFile(String inFilename, String outFilename) {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = AudioFileFunc.AUDIO_SAMPLE_RATE;
        int channels = 2;
        long byteRate = 16 * AudioFileFunc.AUDIO_SAMPLE_RATE * channels / 8;
        byte[] data = new byte[bufferSizeInBytes];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。      
     * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
     * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有自己特有的头文件。
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}
 
   
 
   
然后在录音按钮里的点击事件里写了这么两行行代码:
 
   
  1. //开始进行语音听写  
  2. mIat.startListening(mRecoListener);  
  3. //开始录音并且保存录音到sd卡  
  4. audioRecordFunc.startRecordAndFile();  
                //开始进行语音听写
                mIat.startListening(mRecoListener);
                //开始录音并且保存录音到sd卡
                audioRecordFunc.startRecordAndFile();
这里会发生一个很奇怪的现象,能听写的时候不能录音,能录音的时候不能听写,录音的两个类MediaRecorder、AudioRecord中 MediaRecorder的start()方法中有这么一句话:
The apps should  not start another recording session during recording.
(一个app不应该在录音期间开启另外一个录音),原因我大致理解为麦克风被占用了

 
   
既然不能一边录音一边识别,那我们只好录完音后再上传去识别,嗯,回头看了一下设置的参数
 
   
  1. mIat.setParameter(SpeechConstant.AUDIO_SOURCE, “-2”);  
  2. mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要识别的音频绝对路径“);  
        mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-2");
        mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要识别的音频绝对路径“);
 
   
设置好参数之后
  1. mIat.startListening(mRecoListener);  
mIat.startListening(mRecoListener);
这下可以了,保存的音频文件可以识别,而且识别的速度和讲话的时候是一样的,也就是说,你在第5秒说的话,就是在第五秒开始识别(不考虑网络延迟的时候),这样,就可以实现了我们的需求。
 
   
然而,事情往往没有那么简单,还是回到了原始的问题,也就是前后端点超时或者一分钟过后的音频不再识别!!!

有两个解决方法:
1、自己重新写一个类继承科大讯飞里面的类,重写里面的方法
2、通过流的方式去控制,假如文件流读取没完成,音频不再识别,那就重新激活,直到文件流读取完毕
 
   
 
   
  两秒钟之后我立马放弃了第一种解决方案,源码是长这样子的:
那就只能采用第二种方案了,此时我们要进行相应的参数设置: 
   
 
   
  1. mIat.setParameter(SpeechConstant.AUDIO_SOURCE, “-1”);  
  2. //开始进行语音听写  
  3. mIat.startListening(mRecoListener);  
  4. //然后在进行音频流输入  
  5. mIat.writeAudio(bytes, 0, bytes.length);  
mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
//开始进行语音听写
mIat.startListening(mRecoListener);
//然后在进行音频流输入
mIat.writeAudio(bytes, 0, bytes.length);
 
   
录完音后,根据音频文件读取流。代码我就不粘贴出来了,因为这个方法行不通,原因是因为流读取的速度太快,几分钟的文件一下子就读取完了,端点超时问题还是会出现(ps:希望有个人告诉流能否控制其读取速度,怎么控制),而且就算能控制速度,假如用户录音一个小时的,你不可能又要花一个小时去识别音频吧,这不现实!!
 
   
未完待续...
 
   
 
   
 
   



                </div>

            最近想要做一款语音听写APP,在网上搜索关于如何使用科大讯飞语音的Demo少之又少,又或者是只是单纯的按照文档来实现简单的语音听写,远远不能满足需求,看了几天的文档和自己搜索的一些资料,还有这几天中遇到的一些问题,觉得有必要做一个笔记,能给初学者一些帮助,也顺便理一下这些天的一些收获,本人只是一个初学者,假如有写得不对或者不好的地方,还望大家指出~~

1、首先当然是创建应用,我这里只是使用了语音听写的功能,创建完成后下载SDK,打开是这样子的


2、导入SDK:

将开发工具包中libs目录下的Msc.jar和Sunflower.jar复制到Android工程的libs目录中,将online文件夹里面的子文件粘贴到工程目录src/main/jniLibs(这是Android studio 和eclipse的不同之处)。假如你要使用它自带的UI动画对话框录音,请将assets文件夹以及里面的子文件粘贴到工程目录src/main/下面,完成后如下图:




3、添加权限:

 
 
  1. <!–连接网络权限,用于执行云端语音能力 –>  
  2. <uses-permission android:name=”android.permission.INTERNET”></uses-permission>  
  3. <!–获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 –>  
  4. <uses-permission android:name=”android.permission.RECORD_AUDIO”></uses-permission>  
  5. <!–读取网络信息状态 –>  
  6. <uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”></uses-permission>  
  7. <!–获取当前wifi状态 –>  
  8. <uses-permission android:name=”android.permission.ACCESS_WIFI_STATE”></uses-permission>  
  9. <!–允许程序改变网络连接状态 –>  
  10. <uses-permission android:name=”android.permission.CHANGE_NETWORK_STATE”></uses-permission>  
  11. <!–读取手机信息权限 –>  
  12. <uses-permission android:name=”android.permission.READ_PHONE_STATE”></uses-permission>  
  13. <!–读取联系人权限,上传联系人需要用到此权限 –>  
  14. <uses-permission android:name=”android.permission.READ_CONTACTS”></uses-permission>  
  15. <!–假如我们要保存录音,还需要以下权限–>  
  16. <!– 在SDCard中创建与删除文件权限 –>  
  17. <uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”></uses-permission>  
  18. <!– SD卡权限  –>  
  19. <uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”></uses-permission>  
  20. <!– 允许程序读取或写入系统设置 –>  
  21. <uses-permission android:name=”android.permission.WRITE_SETTINGS”></uses-permission>  
<!--连接网络权限,用于执行云端语音能力 -->
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
<!--读取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<!--获取当前wifi状态 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<!--允许程序改变网络连接状态 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"></uses-permission>
<!--读取手机信息权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<!--读取联系人权限,上传联系人需要用到此权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<!--假如我们要保存录音,还需要以下权限-->
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
<!-- SD卡权限  -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<!-- 允许程序读取或写入系统设置 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"></uses-permission>



4、初始化:

//将“12345678”替换成您申请的APPID,申请地址:http://open.voicecloud.cn

SpeechUtility.createUtility(this, "appid=123456789");
 
 
数据收集接口:
 
 
 
 
 
  1. @Override  
  2. protected void onResume() {  
  3.         // 开放统计 移动数据统计分析  
  4.         FlowerCollector.onResume(MainActivity.this);  
  5.         super.onResume();  
  6. }  
  7.   
  8. @Override  
  9. protected void onPause() {  
  10.         // 开放统计 移动数据统计分析  
  11.         FlowerCollector.onPause(MainActivity.this);  
  12.         super.onPause();  
  13. }  
@Override
protected void onResume() {
        // 开放统计 移动数据统计分析
        FlowerCollector.onResume(MainActivity.this);
        super.onResume();
}

@Override
protected void onPause() {
        // 开放统计 移动数据统计分析
        FlowerCollector.onPause(MainActivity.this);
        super.onPause();
}

说明:
1.确保在所有的 activity 中都调用 FlowerCollector.onResume() FlowerCollector.onPause()方法。这两个调用将不会阻塞应用程序的主线程,也不会影响应用程序的性能。
2.注意,如果您的 Activity 之间有继承或者控制关系请不要同时在父和子 Activity 中重复添加onPause onResume 方法,否则会造成重复统计(eg:使用 TabHostTabActivityActivityGroup )3.一个应用程序在多个 activity 之间连续切换时,会被视为同一个 session(启动)4.当用户两次使用之间间隔超过 30 秒时,将被认为是两个的独立的 session(启动)。例如:用户回到home,或进入其他程序,经过一段时间后再返回之前的应用。
5.所有日志收集工作均在 onResume 之后进行,在 onPause 之后结束。 (ps:其实我也不懂这是干嘛的,猜想是为了收集数据的,还有一些参数设置我就不罗列了,有兴趣的同学可以去看,因为我发现就算没设置它们我也能进行语音听写,反正收集的数据我看不懂)


5、语音听写的使用(采取云端听写的引擎模式,暂时未考虑本地集成)

注意导包:com.iflytek.cloud而不是android.speech.SpeechRecognizer

1)使用自带UI语音对话框

优点:简单方便、美观

缺点:端点超时会自动停止识别(不理解的往下看设置参数说明)默认前段点:5000 后端点:1800

这是使用语音识别最简单的方法,在点击事件上调用start()方法就好了

  1. public void start() {  
  2.         //1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener  
  3.         iatDialog = new RecognizerDialog(this, initListener);  
  4.         //2.设置听写参数  
  5.         iatDialog.setParameter(SpeechConstant.DOMAIN, “iat”);  
  6.         iatDialog.setParameter(SpeechConstant.LANGUAGE, “zh_cn”);  
  7.         iatDialog.setParameter(SpeechConstant.ACCENT, “mandarin ”);  
  8.         //3.设置回调接口  
  9.         iatDialog.setListener(new RecognizerDialogListener() {  
  10.             @Override  
  11.             public void onResult(RecognizerResult recognizerResult, boolean b) {  
  12.                 if (!b) {  
  13.                     String json = recognizerResult.getResultString();  
  14.                     String str = JsonParser.parseIatResult(json);  
  15.                     System.out.println(“说话内容:”+str);  
  16.                     textView.setText(str);  
  17.                 }  
  18.             }  
  19.   
  20.             @Override  
  21.             public void onError(SpeechError speechError) {  
  22.                 Log.d(“error”, speechError.toString());  
  23.             }  
  24.         });  
  25. //4.开始听写  
  26.         iatDialog.show();  
  27.     }  
public void start() {
        //1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener
        iatDialog = new RecognizerDialog(this, initListener);
        //2.设置听写参数
        iatDialog.setParameter(SpeechConstant.DOMAIN, "iat");
        iatDialog.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        iatDialog.setParameter(SpeechConstant.ACCENT, "mandarin ");
        //3.设置回调接口
        iatDialog.setListener(new RecognizerDialogListener() {
            @Override
            public void onResult(RecognizerResult recognizerResult, boolean b) {
                if (!b) {
                    String json = recognizerResult.getResultString();
                    String str = JsonParser.parseIatResult(json);
                    System.out.println("说话内容:"+str);
                    textView.setText(str);
                }
            }

            @Override
            public void onError(SpeechError speechError) {
                Log.d("error", speechError.toString());
            }
        });
//4.开始听写
        iatDialog.show();
    }

增加if(!b)的判断是因为每次说话结束之后,返回数据的最后一次是返回一个标点符号”。”或者”!”之类的,当然你也可以设置不返回标点符号。

  1. //设置是否带标点符号 0表示不带标点,1则表示带标点。  
  2. mIat.setParameter(SpeechConstant.ASR_PTT, ”0”);  
//设置是否带标点符号 0表示不带标点,1则表示带标点。
mIat.setParameter(SpeechConstant.ASR_PTT, "0");

2)不使用自带UI对话框

优点:可以设置自己想要的参数

缺点:使用比较麻烦,对于初学者来讲,根据讲话音量大小自定义麦克风效果的View简直是噩梦(我不会,哪位大神做出来了求分享~~)

简单的用法:

1.创建对象:

  1. <span style=“font-size:18px;”>//1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener  
  2. mIat = SpeechRecognizer.createRecognizer(thisnull);</span>  
<span style="font-size:18px;">//1.创建SpeechRecognizer对象,第二个参数:本地听写时传InitListener
mIat = SpeechRecognizer.createRecognizer(this, null);</span>

  2.设置参数(听写这三个参数是必须的,下面设置时不再提示):
  1. mIat.setParameter(SpeechConstant.DOMAIN, “iat”);  
  2. // 简体中文:”zh_cn”, 美式英文:”en_us”  
  3. mIat.setParameter(SpeechConstant.LANGUAGE, ”zh_cn”);  
  4. //普通话:mandarin(默认)  
  5. //粤 语:cantonese  
  6. //四川话:lmz  
  7. //河南话:henanese  
  8. mIat.setParameter(SpeechConstant.ACCENT, ”mandarin ”);  
mIat.setParameter(SpeechConstant.DOMAIN, "iat");
// 简体中文:"zh_cn", 美式英文:"en_us"
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//普通话:mandarin(默认)
//粤 语:cantonese
//四川话:lmz
//河南话:henanese
mIat.setParameter(SpeechConstant.ACCENT, "mandarin ");
3.实例化监听对象
   
  1. private RecognizerListener recognizerListener = new RecognizerListener() {  
  2.         @Override  
  3.         public void onVolumeChanged(int i, byte[] bytes) {  
  4.   
  5.         }  
  6.   
  7.         @Override  
  8.         public void onBeginOfSpeech() {  
  9.             System.out.println(”开始识别”);  
  10.         }  
  11.   
  12.         @Override  
  13.         public void onEndOfSpeech() {  
  14.             System.out.println(”识别结束”);  
  15.         }  
  16.   
  17.         @Override  
  18.         public void onResult(RecognizerResult recognizerResult, boolean b) {  
  19.             String str=JsonParser.parseIatResult(recognizerResult.getResultString());  
  20.             System.out.println(”识别结果”+str);  
  21.         }  
  22.   
  23.         @Override  
  24.         public void onError(SpeechError speechError) {  
  25.             System.out.println(”识别出错”);  
  26.         }  
  27.   
  28.         @Override  
  29.         public void onEvent(int i, int i1, int i2, Bundle bundle) {  
  30.               
  31.         }  
  32.     };  
private RecognizerListener recognizerListener = new RecognizerListener() {
        @Override
        public void onVolumeChanged(int i, byte[] bytes) {

        }

        @Override
        public void onBeginOfSpeech() {
            System.out.println("开始识别");
        }

        @Override
        public void onEndOfSpeech() {
            System.out.println("识别结束");
        }

        @Override
        public void onResult(RecognizerResult recognizerResult, boolean b) {
            String str=JsonParser.parseIatResult(recognizerResult.getResultString());
            System.out.println("识别结果"+str);
        }

        @Override
        public void onError(SpeechError speechError) {
            System.out.println("识别出错");
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    };
4.添加监听
 
 
  1. mIat.startListening(recognizerListener);  
mIat.startListening(recognizerListener);

官方Demo给出的解析Json的类:
 
 
  1. package com.hxl.voicetest1;  
  2.   
  3. import org.json.JSONArray;  
  4. import org.json.JSONObject;  
  5. import org.json.JSONTokener;  
  6.   
  7. /** 
  8.  * Json结果解析类 
  9.  */  
  10. public class JsonParser {  
  11.   
  12.     public static String parseIatResult(String json) {  
  13.         StringBuffer ret = new StringBuffer();  
  14.         try {  
  15.             JSONTokener tokener = new JSONTokener(json);  
  16.             JSONObject joResult = new JSONObject(tokener);  
  17.   
  18.             JSONArray words = joResult.getJSONArray(”ws”);  
  19.             for (int i = 0; i < words.length(); i++) {  
  20.                 // 转写结果词,默认使用第一个结果  
  21.                 JSONArray items = words.getJSONObject(i).getJSONArray(”cw”);  
  22.                 JSONObject obj = items.getJSONObject(0);  
  23.                 ret.append(obj.getString(”w”));  
  24.                 //如果需要多候选结果,解析数组其他字段  
  25.                 //for(int j = 0; j < items.length(); j++)  
  26.                 //{  
  27.                 //JSONObject obj = items.getJSONObject(j);  
  28.                 //ret.append(obj.getString(“w”));  
  29.                 //}  
  30.             }  
  31.         } catch (Exception e) {  
  32.             e.printStackTrace();  
  33.         }  
  34.         return ret.toString();  
  35.     }  
  36.   
  37.     public static String parseGrammarResult(String json) {  
  38.         StringBuffer ret = new StringBuffer();  
  39.         try {  
  40.             JSONTokener tokener = new JSONTokener(json);  
  41.             JSONObject joResult = new JSONObject(tokener);  
  42.   
  43.             JSONArray words = joResult.getJSONArray(”ws”);  
  44.             for (int i = 0; i < words.length(); i++) {  
  45.                 JSONArray items = words.getJSONObject(i).getJSONArray(”cw”);  
  46.                 for (int j = 0; j < items.length(); j++) {  
  47.                     JSONObject obj = items.getJSONObject(j);  
  48.                     if (obj.getString(“w”).contains(“nomatch”)) {  
  49.                         ret.append(”没有匹配结果.”);  
  50.                         return ret.toString();  
  51.                     }  
  52.                     ret.append(”【结果】” + obj.getString(“w”));  
  53.                     ret.append(”【置信度】” + obj.getInt(“sc”));  
  54.                     ret.append(”n”);  
  55.                 }  
  56.             }  
  57.         } catch (Exception e) {  
  58.             e.printStackTrace();  
  59.             ret.append(”没有匹配结果.”);  
  60.         }  
  61.         return ret.toString();  
  62.     }  
  63. }  
package com.hxl.voicetest1;

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

/**
 * Json结果解析类
 */
public class JsonParser {

    public static String parseIatResult(String json) {
        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"));
                //如果需要多候选结果,解析数组其他字段
                //for(int j = 0; j < items.length(); j++)
                //{
                //JSONObject obj = items.getJSONObject(j);
                //ret.append(obj.getString("w"));
                //}
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret.toString();
    }

    public static String parseGrammarResult(String json) {
        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");
                for (int j = 0; j < items.length(); j++) {
                    JSONObject obj = items.getJSONObject(j);
                    if (obj.getString("w").contains("nomatch")) {
                        ret.append("没有匹配结果.");
                        return ret.toString();
                    }
                    ret.append("【结果】" + obj.getString("w"));
                    ret.append("【置信度】" + obj.getInt("sc"));
                    ret.append("n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            ret.append("没有匹配结果.");
        }
        return ret.toString();
    }
}

到这里,科大讯飞语音最简单的听写Demo就算完成了。这时你们会想:WTF?就这些我还不如自个儿看文档…..
 
 
当然了,既然写这篇博客,肯定不仅仅是简单介绍科大讯飞语音听写的使用而已。
 
 
我想要的效果是:无限制时间录音,并且我想知道我在第几秒说了什么话,回放录音的时候,播放到哪就显示相应的文字,还可以对识别错误的字段进行纠错修改。
 
 
我们来看一下一些常用的设置参数(个人认为),我们可以根据我们的需求来设置相应的参数
 
 
  1. // 清空参数  
  2. mIat.setParameter(SpeechConstant.PARAMS, null);  
  3. //短信和日常用语:iat (默认)  视频:video  地图:poi  音乐:music  
  4. mIat.setParameter(SpeechConstant.DOMAIN, ”iat”);  
  5. // 简体中文:”zh_cn”, 美式英文:”en_us”  
  6. mIat.setParameter(SpeechConstant.LANGUAGE, ”zh_cn”);  
  7. //普通话:mandarin(默认)  
  8. //粤 语:cantonese  
  9. //四川话:lmz  
  10. //河南话:henanese<span style=”font-family: Menlo;”>     </span>  
  11. mIat.setParameter(SpeechConstant.ACCENT, ”mandarin ”);  
  12. // 设置听写引擎 “cloud”, “local”,”mixed”  在线  本地  混合  
  13. //本地的需要本地功能集成  
  14. mIat.setParameter(SpeechConstant.ENGINE_TYPE, ”cloud”);  
  15. // 设置返回结果格式 听写会话支持json和plain  
  16. mIat.setParameter(SpeechConstant.RESULT_TYPE, ”json”);  
  17. //设置是否带标点符号 0表示不带标点,1则表示带标点。  
  18. mIat.setParameter(SpeechConstant.ASR_PTT, ”0”);  
  19. //只有设置这个属性为1时,VAD_BOS  VAD_EOS才会生效,且RecognizerListener.onVolumeChanged才有音量返回默认:1  
  20. mIat.setParameter(SpeechConstant.VAD_ENABLE,”1”);  
  21. // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理1000~10000  
  22. mIat.setParameter(SpeechConstant.VAD_BOS, ”5000”);  
  23. // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音0~10000  
  24. mIat.setParameter(SpeechConstant.VAD_EOS, ”1800”);  
  25. // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限  
  26. // 注:AUDIO_FORMAT参数语记需要更新版本才能生效  
  27. mIat.setParameter(SpeechConstant.AUDIO_FORMAT, ”wav”);  
  28. //设置识别会话被中断时(如当前会话未结束就开启了新会话等),  
  29. //是否通 过RecognizerListener.onError(com.iflytek.cloud.SpeechError)回调ErrorCode.ERROR_INTERRUPT错误。  
  30. //默认false    [null,true,false]  
  31. mIat.setParameter(SpeechConstant.ASR_INTERRUPT_ERROR,”false”);  
  32. //音频采样率  8000~16000  默认:16000  
  33. mIat.setParameter(SpeechConstant.SAMPLE_RATE,”16000”);  
  34. //默认:麦克风(1)(MediaRecorder.AudioSource.MIC)  
  35. //在写音频流方式(-1)下,应用层通过writeAudio函数送入音频;  
  36. //在传文件路径方式(-2)下,SDK通过应用层设置的ASR_SOURCE_PATH值, 直接读取音频文件。目前仅在SpeechRecognizer中支持。  
  37. mIat.setParameter(SpeechConstant.AUDIO_SOURCE, ”-1”);  
  38. //保存音频文件的路径   仅支持pcm和wav  
  39. mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + ”test.wav”);  
        // 清空参数
        mIat.setParameter(SpeechConstant.PARAMS, null);
        //短信和日常用语:iat (默认)  视频:video  地图:poi  音乐:music
        mIat.setParameter(SpeechConstant.DOMAIN, "iat");
        // 简体中文:"zh_cn", 美式英文:"en_us"
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        //普通话:mandarin(默认)
        //粤 语:cantonese
        //四川话:lmz
        //河南话:henanese<span style="font-family: Menlo;">     </span>
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin ");
        // 设置听写引擎 "cloud", "local","mixed"  在线  本地  混合
        //本地的需要本地功能集成
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, "cloud");
        // 设置返回结果格式 听写会话支持json和plain
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
        //设置是否带标点符号 0表示不带标点,1则表示带标点。
        mIat.setParameter(SpeechConstant.ASR_PTT, "0");
        //只有设置这个属性为1时,VAD_BOS  VAD_EOS才会生效,且RecognizerListener.onVolumeChanged才有音量返回默认:1
        mIat.setParameter(SpeechConstant.VAD_ENABLE,"1");
        // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理1000~10000
        mIat.setParameter(SpeechConstant.VAD_BOS, "5000");
        // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音0~10000
        mIat.setParameter(SpeechConstant.VAD_EOS, "1800");
        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        //设置识别会话被中断时(如当前会话未结束就开启了新会话等),
        //是否通 过RecognizerListener.onError(com.iflytek.cloud.SpeechError)回调ErrorCode.ERROR_INTERRUPT错误。
        //默认false    [null,true,false]
        mIat.setParameter(SpeechConstant.ASR_INTERRUPT_ERROR,"false");
        //音频采样率  8000~16000  默认:16000
        mIat.setParameter(SpeechConstant.SAMPLE_RATE,"16000");
        //默认:麦克风(1)(MediaRecorder.AudioSource.MIC)
        //在写音频流方式(-1)下,应用层通过writeAudio函数送入音频;
        //在传文件路径方式(-2)下,SDK通过应用层设置的ASR_SOURCE_PATH值, 直接读取音频文件。目前仅在SpeechRecognizer中支持。
        mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
        //保存音频文件的路径   仅支持pcm和wav
        mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "test.wav");


我首先想到当然就是设置音频保存路径了啊,这还不简单
 
 
 
 
  1. mIat.setParameter(SpeechConstant.AUDIO_FORMAT, “wav”);  
  2. mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要保存的路径”);  
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, "要保存的路径");

 
 
时间戳这个好办,根据录音开始时记录当前时间startTime,在RecognizerListener的onResult方法获取当前时间currentTime,然后用currentTime-startTime 就是第几秒说的话了(由于云端识别会有延迟,这个秒数其实是不正确的,这里先忽略这个问题)。

结束了?
不是,我们再来看一下科大讯飞的说明文档中
 
 
 
 
 
 
科大讯飞对语音听写做了限制,端点超时最大也只能设置10秒,超过这个时间识别自动终止,不再对后续的语音部分进行识别。假如我们要长时间录音,不可能让用户每10秒钟就要说一句话,而且还有一个是值得我们注意的:
 
 
 
 
 
 
也就是说,就算我们连续不停的讲话,音频录制最多也就是60秒而已,怎么办?
这时候我就想,我用自己的方法录音,用科大讯飞的去识别,这是个好方法,我立马在百度输入框敲上 ”Android录音
这里推荐一个大神封装的录音类:http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html
 
 
 
 
考虑到科大讯飞只支持wav、pcm文件,我只是把AudioRecordFunc类copy过来,考虑到科大讯飞对音频文件识别的要求:

上传音频的采样率与采样精度:A:采样率16KHZ或者8KHZ,单声道,采样精度16bit的PCM或者WAV格式的音频

我将代码进行了部分修改。
 
 
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6.   
  7. import android.media.AudioFormat;  
  8. import android.media.AudioRecord;  
  9.   
  10. public class AudioRecordFunc {  
  11.     // 缓冲区字节大小    
  12.     private int bufferSizeInBytes = 0;  
  13.   
  14.     //AudioName裸音频数据文件 ,麦克风  
  15.     private String AudioName = “”;  
  16.   
  17.     //NewAudioName可播放的音频文件    
  18.     private String NewAudioName = “”;  
  19.   
  20.     private AudioRecord audioRecord;  
  21.     private boolean isRecord = false;// 设置正在录制的状态    
  22.   
  23.   
  24.     private static AudioRecordFunc mInstance;  
  25.   
  26.     private AudioRecordFunc() {  
  27.   
  28.     }  
  29.   
  30.     public synchronized static AudioRecordFunc getInstance() {  
  31.         if (mInstance == null)  
  32.             mInstance = new AudioRecordFunc();  
  33.         return mInstance;  
  34.     }  
  35.   
  36.     public int startRecordAndFile() {  
  37.         //判断是否有外部存储设备sdcard  
  38.         if (AudioFileFunc.isSdcardExit()) {  
  39.             if (isRecord) {  
  40.                 return ErrorCode.E_STATE_RECODING;  
  41.             } else {  
  42.                 if (audioRecord == null)  
  43.                     creatAudioRecord();  
  44.   
  45.                 audioRecord.startRecording();  
  46.                 // 让录制状态为true    
  47.                 isRecord = true;  
  48.                 // 开启音频文件写入线程    
  49.                 new Thread(new AudioRecordThread()).start();  
  50.   
  51.                 return ErrorCode.SUCCESS;  
  52.             }  
  53.   
  54.         } else {  
  55.             return ErrorCode.E_NOSDCARD;  
  56.         }  
  57.   
  58.     }  
  59.   
  60.     public void stopRecordAndFile() {  
  61.         close();  
  62.     }  
  63.   
  64.   
  65.     public long getRecordFileSize() {  
  66.         return AudioFileFunc.getFileSize(NewAudioName);  
  67.     }  
  68.   
  69.   
  70.     private void close() {  
  71.         if (audioRecord != null) {  
  72.             System.out.println(”stopRecord”);  
  73.             isRecord = false;//停止文件写入    
  74.             audioRecord.stop();  
  75.             audioRecord.release();//释放资源    
  76.             audioRecord = null;  
  77.         }  
  78.     }  
  79.   
  80.   
  81.     private void creatAudioRecord() {  
  82.         // 获取音频文件路径  
  83.         AudioName = AudioFileFunc.getRawFilePath();  
  84.         NewAudioName = AudioFileFunc.getWavFilePath();  
  85.   
  86.         // 获得缓冲区字节大小    
  87.         bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE,  
  88.                 AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);  
  89.   
  90.   
  91.         // 创建AudioRecord对象(修改处)  
  92.         audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_ IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);  
  93.     }  
  94.   
  95.     class AudioRecordThread implements Runnable {  
  96.         @Override  
  97.         public void run() {  
  98.             writeDateTOFile();//往文件中写入裸数据  
  99.             copyWaveFile(AudioName, NewAudioName);//给裸数据加上头文件  
  100.         }  
  101.     }  
  102.   
  103.     /** 
  104.      * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频 
  105.      * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理 
  106.      * 比如你要做一个爱说话的TOM猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。 
  107.      */  
  108.     private void writeDateTOFile() {  
  109.         // new一个byte数组用来存一些字节数据,大小为缓冲区大小  
  110.         byte[] audiodata = new byte[bufferSizeInBytes];  
  111.         FileOutputStream fos = null;  
  112.         int readsize = 0;  
  113.         try {  
  114.             File file = new File(AudioName);  
  115.             if (file.exists()) {  
  116.                 file.delete();  
  117.             }  
  118.             fos = new FileOutputStream(file);  
  119.             // 建立一个可存取字节的文件  
  120.         } catch (Exception e) {  
  121.             e.printStackTrace();  
  122.         }  
  123.         while (isRecord == true) {  
  124.             readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);  
  125.             if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {  
  126.                 try {  
  127.                     fos.write(audiodata);  
  128.                 } catch (IOException e) {  
  129.                     e.printStackTrace();  
  130.                 }  
  131.             }  
  132.         }  
  133.         try {  
  134.             if (fos != null)  
  135.                 fos.close();// 关闭写入流  
  136.         } catch (IOException e) {  
  137.             e.printStackTrace();  
  138.         }  
  139.     }  
  140.   
  141.     // 这里得到可播放的音频文件  
  142.     private void copyWaveFile(String inFilename, String outFilename) {  
  143.         FileInputStream in = null;  
  144.         FileOutputStream out = null;  
  145.         long totalAudioLen = 0;  
  146.         long totalDataLen = totalAudioLen + 36;  
  147.         long longSampleRate = AudioFileFunc.AUDIO_SAMPLE_RATE;  
  148.         int channels = 2;  
  149.         long byteRate = 16 * AudioFileFunc.AUDIO_SAMPLE_RATE * channels / 8;  
  150.         byte[] data = new byte[bufferSizeInBytes];  
  151.         try {  
  152.             in = new FileInputStream(inFilename);  
  153.             out = new FileOutputStream(outFilename);  
  154.             totalAudioLen = in.getChannel().size();  
  155.             totalDataLen = totalAudioLen + 36;  
  156.             WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);  
  157.             while (in.read(data) != -1) {  
  158.                 out.write(data);  
  159.             }  
  160.             in.close();  
  161.             out.close();  
  162.         } catch (FileNotFoundException e) {  
  163.             e.printStackTrace();  
  164.         } catch (IOException e) {  
  165.             e.printStackTrace();  
  166.         }  
  167.     }  
  168.   
  169.     /** 
  170.      * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。       
  171.      * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav 
  172.      * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有自己特有的头文件。 
  173.      */  
  174.     private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {  
  175.         byte[] header = new byte[44];  
  176.         header[0] = ‘R’// RIFF/WAVE header  
  177.         header[1] = ‘I’;  
  178.         header[2] = ‘F’;  
  179.         header[3] = ‘F’;  
  180.         header[4] = (byte) (totalDataLen & 0xff);  
  181.         header[5] = (byte) ((totalDataLen >> 8) & 0xff);  
  182.         header[6] = (byte) ((totalDataLen >> 16) & 0xff);  
  183.         header[7] = (byte) ((totalDataLen >> 24) & 0xff);  
  184.         header[8] = ‘W’;  
  185.         header[9] = ‘A’;  
  186.         header[10] = ‘V’;  
  187.         header[11] = ‘E’;  
  188.         header[12] = ‘f’// ‘fmt ’ chunk  
  189.         header[13] = ‘m’;  
  190.         header[14] = ‘t’;  
  191.         header[15] = ‘ ’;  
  192.         header[16] = 16// 4 bytes: size of ‘fmt ’ chunk  
  193.         header[17] = 0;  
  194.         header[18] = 0;  
  195.         header[19] = 0;  
  196.         header[20] = 1// format = 1  
  197.         header[21] = 0;  
  198.         header[22] = (byte) channels;  
  199.         header[23] = 0;  
  200.         header[24] = (byte) (longSampleRate & 0xff);  
  201.         header[25] = (byte) ((longSampleRate >> 8) & 0xff);  
  202.         header[26] = (byte) ((longSampleRate >> 16) & 0xff);  
  203.         header[27] = (byte) ((longSampleRate >> 24) & 0xff);  
  204.         header[28] = (byte) (byteRate & 0xff);  
  205.         header[29] = (byte) ((byteRate >> 8) & 0xff);  
  206.         header[30] = (byte) ((byteRate >> 16) & 0xff);  
  207.         header[31] = (byte) ((byteRate >> 24) & 0xff);  
  208.         header[32] = (byte) (2 * 16 / 8); // block align  
  209.         header[33] = 0;  
  210.         header[34] = 16// bits per sample  
  211.         header[35] = 0;  
  212.         header[36] = ‘d’;  
  213.         header[37] = ‘a’;  
  214.         header[38] = ‘t’;  
  215.         header[39] = ‘a’;  
  216.         header[40] = (byte) (totalAudioLen & 0xff);  
  217.         header[41] = (byte) ((totalAudioLen >> 8) & 0xff);  
  218.         header[42] = (byte) ((totalAudioLen >> 16) & 0xff);  
  219.         header[43] = (byte) ((totalAudioLen >> 24) & 0xff);  
  220.         out.write(header, 044);  
  221.     }  
  222. }  
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.media.AudioFormat;
import android.media.AudioRecord;

public class AudioRecordFunc {
    // 缓冲区字节大小  
    private int bufferSizeInBytes = 0;

    //AudioName裸音频数据文件 ,麦克风
    private String AudioName = "";

    //NewAudioName可播放的音频文件  
    private String NewAudioName = "";

    private AudioRecord audioRecord;
    private boolean isRecord = false;// 设置正在录制的状态  


    private static AudioRecordFunc mInstance;

    private AudioRecordFunc() {

    }

    public synchronized static AudioRecordFunc getInstance() {
        if (mInstance == null)
            mInstance = new AudioRecordFunc();
        return mInstance;
    }

    public int startRecordAndFile() {
        //判断是否有外部存储设备sdcard
        if (AudioFileFunc.isSdcardExit()) {
            if (isRecord) {
                return ErrorCode.E_STATE_RECODING;
            } else {
                if (audioRecord == null)
                    creatAudioRecord();

                audioRecord.startRecording();
                // 让录制状态为true  
                isRecord = true;
                // 开启音频文件写入线程  
                new Thread(new AudioRecordThread()).start();

                return ErrorCode.SUCCESS;
            }

        } else {
            return ErrorCode.E_NOSDCARD;
        }

    }

    public void stopRecordAndFile() {
        close();
    }


    public long getRecordFileSize() {
        return AudioFileFunc.getFileSize(NewAudioName);
    }


    private void close() {
        if (audioRecord != null) {
            System.out.println("stopRecord");
            isRecord = false;//停止文件写入  
            audioRecord.stop();
            audioRecord.release();//释放资源  
            audioRecord = null;
        }
    }


    private void creatAudioRecord() {
        // 获取音频文件路径
        AudioName = AudioFileFunc.getRawFilePath();
        NewAudioName = AudioFileFunc.getWavFilePath();

        // 获得缓冲区字节大小  
        bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);


        // 创建AudioRecord对象(修改处)
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_ IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
    }

    class AudioRecordThread implements Runnable {
        @Override
        public void run() {
            writeDateTOFile();//往文件中写入裸数据
            copyWaveFile(AudioName, NewAudioName);//给裸数据加上头文件
        }
    }

    /**
     * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频
     * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理
     * 比如你要做一个爱说话的TOM猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。
     */
    private void writeDateTOFile() {
        // new一个byte数组用来存一些字节数据,大小为缓冲区大小
        byte[] audiodata = new byte[bufferSizeInBytes];
        FileOutputStream fos = null;
        int readsize = 0;
        try {
            File file = new File(AudioName);
            if (file.exists()) {
                file.delete();
            }
            fos = new FileOutputStream(file);
            // 建立一个可存取字节的文件
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (isRecord == true) {
            readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
                try {
                    fos.write(audiodata);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        try {
            if (fos != null)
                fos.close();// 关闭写入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 这里得到可播放的音频文件
    private void copyWaveFile(String inFilename, String outFilename) {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = AudioFileFunc.AUDIO_SAMPLE_RATE;
        int channels = 2;
        long byteRate = 16 * AudioFileFunc.AUDIO_SAMPLE_RATE * channels / 8;
        byte[] data = new byte[bufferSizeInBytes];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。      
     * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
     * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有自己特有的头文件。
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}
 
 
 
 
然后在录音按钮里的点击事件里写了这么两行行代码:
 
 
  1. //开始进行语音听写  
  2. mIat.startListening(mRecoListener);  
  3. //开始录音并且保存录音到sd卡  
  4. audioRecordFunc.startRecordAndFile();  
                //开始进行语音听写
                mIat.startListening(mRecoListener);
                //开始录音并且保存录音到sd卡
                audioRecordFunc.startRecordAndFile();
这里会发生一个很奇怪的现象,能听写的时候不能录音,能录音的时候不能听写,录音的两个类MediaRecorder、AudioRecord中 MediaRecorder的start()方法中有这么一句话:
The apps should  not start another recording session during recording.
(一个app不应该在录音期间开启另外一个录音),原因我大致理解为麦克风被占用了

 
 
既然不能一边录音一边识别,那我们只好录完音后再上传去识别,嗯,回头看了一下设置的参数
 
 
  1. mIat.setParameter(SpeechConstant.AUDIO_SOURCE, “-2”);  
  2. mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要识别的音频绝对路径“);  
        mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-2");
        mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, ”要识别的音频绝对路径“);
 
 
设置好参数之后
  1. mIat.startListening(mRecoListener);  
mIat.startListening(mRecoListener);
这下可以了,保存的音频文件可以识别,而且识别的速度和讲话的时候是一样的,也就是说,你在第5秒说的话,就是在第五秒开始识别(不考虑网络延迟的时候),这样,就可以实现了我们的需求。
 
 
然而,事情往往没有那么简单,还是回到了原始的问题,也就是前后端点超时或者一分钟过后的音频不再识别!!!

有两个解决方法:
1、自己重新写一个类继承科大讯飞里面的类,重写里面的方法
2、通过流的方式去控制,假如文件流读取没完成,音频不再识别,那就重新激活,直到文件流读取完毕
 
 
 
 
  两秒钟之后我立马放弃了第一种解决方案,源码是长这样子的:
那就只能采用第二种方案了,此时我们要进行相应的参数设置: 
 
 
 
  1. mIat.setParameter(SpeechConstant.AUDIO_SOURCE, “-1”);  
  2. //开始进行语音听写  
  3. mIat.startListening(mRecoListener);  
  4. //然后在进行音频流输入  
  5. mIat.writeAudio(bytes, 0, bytes.length);  
mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
//开始进行语音听写
mIat.startListening(mRecoListener);
//然后在进行音频流输入
mIat.writeAudio(bytes, 0, bytes.length);
 
 
录完音后,根据音频文件读取流。代码我就不粘贴出来了,因为这个方法行不通,原因是因为流读取的速度太快,几分钟的文件一下子就读取完了,端点超时问题还是会出现(ps:希望有个人告诉流能否控制其读取速度,怎么控制),而且就算能控制速度,假如用户录音一个小时的,你不可能又要花一个小时去识别音频吧,这不现实!!
 
 
未完待续...
 
 
 
 
 
 



                </div>

猜你喜欢

转载自blog.csdn.net/csw19970124/article/details/80058608