├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── Msc.jar │ └── Sunflower.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── alvin │ │ └── speechdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── iattest.wav │ │ ├── userwords.txt │ │ └── voice.amr │ ├── java │ │ └── com │ │ │ └── alvin │ │ │ └── speechdemo │ │ │ ├── MainActivity.java │ │ │ └── util │ │ │ ├── AudioDecode.java │ │ │ ├── FucUtil.java │ │ │ └── JsonParser.java │ ├── jniLibs │ │ └── armeabi │ │ │ └── libmsc.so │ └── res │ │ ├── anim │ │ ├── push_fade_in.xml │ │ └── push_fade_out.xml │ │ ├── drawable-xhdpi │ │ └── img_view_loading.png │ │ ├── layout │ │ ├── activity_login.xml │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── alvin │ └── speechdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpeechDemo 2 | 结合讯飞语音,使用封装好的工具类,把MediaRecord录制好的本地录音amr格式音频转成文字 3 | MediaRecorder的关键设置项代码如下 4 | ```Java 5 | MediaRecorder mRecorder = new MediaRecorder(); 6 | mRecorder.setMaxDuration(60500); 7 | mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 8 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) { 9 | mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); 10 | } else { 11 | mRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 12 | } 13 | mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 14 | mRecorder.setOutputFile(mFileName); 15 | mRecorder.prepare(); 16 | mRecorder.start(); 17 | ``` 18 | 19 | 因时间仓促就没有把录音部分放入demo还请见谅,demo中在access中放置了录制好的音频,可以拿出来放到手机sd卡相应目录中,或者自己定义的一个路径使用方法如下,也可以参考[我的博客](http://blog.csdn.net/u010705554/article/details/53189317) 20 | ```Java 21 | /** 22 | * 工具类 23 | * @param audioPath 24 | */ 25 | private void audioDecodeFun(String audioPath){ 26 | audioDecode = AudioDecode.newInstance(); 27 | audioDecode.setFilePath(audioPath); 28 | audioDecode.prepare(); 29 | audioDecode.setOnCompleteListener(new AudioDecode.OnCompleteListener() { 30 | @Override 31 | public void completed(final ArrayList pcmData) { 32 | if(pcmData!=null){ 33 | //写入音频文件数据,数据格式必须是采样率为8KHz或16KHz(本地识别只支持16K采样率,云端都支持),位长16bit,单声道的wav或者pcm 34 | //必须要先保存到本地,才能被讯飞识别 35 | //为防止数据较长,多次写入,把一次写入的音频,限制到 64K 以下,然后循环的调用wirteAudio,直到把音频写完为止 36 | for (byte[] data : pcmData){ 37 | mIat.writeAudio(data, 0, data.length); 38 | } 39 | Log.d("-----------stop",System.currentTimeMillis()+""); 40 | mIat.stopListening(); 41 | }else{ 42 | mIat.cancel(); 43 | Log.d(TAG,"--->读取音频流失败"); 44 | } 45 | audioDecode.release(); 46 | } 47 | }); 48 | audioDecode.startAsync(); 49 | } 50 | 51 | ``` 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | defaultConfig { 7 | applicationId "com.alvin.speechdemo" 8 | minSdkVersion 9 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:23.3.0' 28 | testCompile 'junit:junit:4.12' 29 | compile 'com.squareup.picasso:picasso:2.5.2' 30 | compile 'com.github.bumptech.glide:glide:3.7.0' 31 | } 32 | -------------------------------------------------------------------------------- /app/libs/Msc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alvin3225/SpeechDemo/98584925e592f53a9846585fe3afc9fc09df5a7e/app/libs/Msc.jar -------------------------------------------------------------------------------- /app/libs/Sunflower.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alvin3225/SpeechDemo/98584925e592f53a9846585fe3afc9fc09df5a7e/app/libs/Sunflower.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\AndroidSDK\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/alvin/speechdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.alvin.speechdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.alvin.speechdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/assets/iattest.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alvin3225/SpeechDemo/98584925e592f53a9846585fe3afc9fc09df5a7e/app/src/main/assets/iattest.wav -------------------------------------------------------------------------------- /app/src/main/assets/userwords.txt: -------------------------------------------------------------------------------- 1 | {"userword":[{"name":"我的常用词","words":["测试常用词","测试常用词2","测试常用词3"]}]} -------------------------------------------------------------------------------- /app/src/main/assets/voice.amr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alvin3225/SpeechDemo/98584925e592f53a9846585fe3afc9fc09df5a7e/app/src/main/assets/voice.amr -------------------------------------------------------------------------------- /app/src/main/java/com/alvin/speechdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.alvin.speechdemo; 2 | 3 | import android.app.Activity; 4 | import android.os.Environment; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | import android.widget.Toast; 10 | 11 | import com.alvin.speechdemo.util.AudioDecode; 12 | import com.alvin.speechdemo.util.FucUtil; 13 | import com.iflytek.cloud.ErrorCode; 14 | import com.iflytek.cloud.LexiconListener; 15 | import com.iflytek.cloud.RecognizerListener; 16 | import com.iflytek.cloud.RecognizerResult; 17 | import com.iflytek.cloud.SpeechConstant; 18 | import com.iflytek.cloud.SpeechError; 19 | import com.iflytek.cloud.SpeechRecognizer; 20 | import com.iflytek.cloud.SpeechUtility; 21 | import com.iflytek.sunflower.FlowerCollector; 22 | import com.alvin.speechdemo.util.JsonParser; 23 | 24 | import org.json.JSONObject; 25 | 26 | import java.io.File; 27 | import java.util.ArrayList; 28 | import java.util.HashMap; 29 | import java.util.LinkedHashMap; 30 | 31 | public class MainActivity extends Activity implements View.OnClickListener{ 32 | 33 | private final String TAG = "SpeechDemo:ID"; 34 | private EditText et_voice_text; 35 | private Toast mToast; 36 | // 用HashMap存储听写结果 37 | private HashMap mIatResults = new LinkedHashMap<>(); 38 | private SpeechRecognizer mIat; 39 | // 引擎类型 40 | private String mEngineType = SpeechConstant.TYPE_CLOUD; 41 | private AudioDecode audioDecode; 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_main); 47 | 48 | init(); 49 | initView(); 50 | } 51 | 52 | private void init(){ 53 | mToast = Toast.makeText(MainActivity.this, "", Toast.LENGTH_SHORT); 54 | //创建语音配置对象,换成自己的id,另外so文件也要换成自己的,AndroidManifest里面还有个统计分析的id也换成自己的 55 | SpeechUtility.createUtility(MainActivity.this, SpeechConstant.APPID+"=5227fccc"); 56 | //1、创建SpeechRecognizer对象,第二个参数:本地识别时传InitListener 57 | mIat = SpeechRecognizer.createRecognizer(MainActivity.this,null); 58 | setParam(); 59 | } 60 | 61 | private void initView(){ 62 | et_voice_text = (EditText) findViewById(R.id.et_voice_text); 63 | } 64 | 65 | //听写监听器 66 | private RecognizerListener mRecognizerListener = new RecognizerListener() { 67 | 68 | //volume音量值0~30,data音频数据 69 | @Override 70 | public void onVolumeChanged(int volume, byte[] bytes) { 71 | showTip("当前正在说话,音量大小:" + volume); 72 | Log.d(TAG, "返回音频数据:"+bytes.length); 73 | } 74 | //开始录音 75 | // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入 76 | @Override 77 | public void onBeginOfSpeech() { 78 | showTip("开始说话"); 79 | } 80 | //结束录音 81 | @Override 82 | public void onEndOfSpeech() { 83 | // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入 84 | showTip("结束说话"); 85 | } 86 | 87 | /** 88 | * 听写结果回调接口,返回Json格式结果 89 | * 一般情况下会通过onResults接口多次返回结果,完整的识别内容是多次结果的累加 90 | * isLast等于true时会话结束。 91 | */ 92 | @Override 93 | public void onResult(RecognizerResult recognizerResult, boolean b) { 94 | Log.d(TAG, recognizerResult.getResultString()); 95 | printResult(recognizerResult); 96 | } 97 | 98 | //会话发生错误回调接口 99 | // Tips: 100 | // 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。 101 | @Override 102 | public void onError(SpeechError speechError) { 103 | //打印错误码描述 104 | Log.d(TAG, "error:" + speechError.getPlainDescription(true)); 105 | showTip(speechError.getPlainDescription(true)); 106 | } 107 | //扩展用接口 108 | @Override 109 | public void onEvent(int eventType, int arg1, int arg2, Bundle bundle) { 110 | 111 | } 112 | }; 113 | 114 | private void printResult(RecognizerResult recognizerResult) { 115 | String text = JsonParser.parseIatResult(recognizerResult.getResultString()); 116 | String sn = null; 117 | //读取Json结果中的sn字段 118 | try { 119 | JSONObject resultJson = new JSONObject(recognizerResult.getResultString()); 120 | sn = resultJson.optString("sn"); 121 | }catch (Exception e){ 122 | e.printStackTrace(); 123 | } 124 | mIatResults.put(sn,text); 125 | StringBuilder sb = new StringBuilder(); 126 | for (String key:mIatResults.keySet()){ 127 | sb.append(mIatResults.get(key)); 128 | } 129 | et_voice_text.setText(sb.toString()); 130 | et_voice_text.setSelection(et_voice_text.length()); 131 | } 132 | 133 | private LexiconListener mLexiconListener = new LexiconListener() { 134 | @Override 135 | public void onLexiconUpdated(String lexiconId, SpeechError speechError) { 136 | if (speechError != null) { 137 | Log.d(TAG,"上传词表:"+speechError.toString()); 138 | } else { 139 | Log.d(TAG,"上传词表:成功"); 140 | } 141 | } 142 | }; 143 | 144 | int ret = 0; // 函数调用返回值 145 | @Override 146 | public void onClick(View v) { 147 | switch (v.getId()){ 148 | case R.id.bt_start: 149 | // 开始听写 150 | // 如何判断一次听写结束:OnResult isLast=true 或者 onError 151 | // 移动数据分析,收集开始听写事件 152 | FlowerCollector.onEvent(MainActivity.this,"iat_recognize"); 153 | et_voice_text.setText(null); 154 | mIatResults.clear(); 155 | 156 | // 不显示听写对话框 157 | ret = mIat.startListening(mRecognizerListener); 158 | if (ret != ErrorCode.SUCCESS) { 159 | showTip("听写失败,错误码:" + ret); 160 | } else { 161 | showTip(getString(R.string.text_begin)); 162 | } 163 | break; 164 | case R.id.bt_upload: 165 | uploadUserWords(); 166 | break; 167 | case R.id.bt_read_voice: 168 | et_voice_text.setText(null);// 清空显示内容 169 | mIatResults.clear(); 170 | setParam(); 171 | // 设置音频来源为外部文件 172 | String mFileDirName = Environment.getExternalStorageDirectory().toString() 173 | + File.separator+ "ID"+ File.separator + "download"; 174 | String audioPath = mFileDirName + File.separator + "voice.amr"; 175 | String mFileName2 = mFileDirName + File.separator + "test_temp.wav"; 176 | mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1"); 177 | mIat.setParameter(SpeechConstant.SAMPLE_RATE, "8000");//设置正确的采样率 178 | mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, mFileName2); 179 | mIat.startListening(mRecognizerListener); 180 | //主要参数: -i 设定输入流 -f 设定输出格式 -ss 开始时间 181 | // 视频参数: 182 | // -b 设定视频流量,默认为200Kbit/s -r 设定帧速率, 183 | // 默认为25 -s 设定画面的宽与高 -aspect 设定画面的比例 184 | // -vn 不处理视频 -vcodec 设定视频编解码器,未设定时则使用与输入流相同的编解码器 185 | // 音频参数: 186 | // -ar 设定采样率 -ac 设定声音的Channel数 -acodec 设定声音编解码器,未设定时则使用与输入流相同的编解码器 -an 不处理音频 187 | 188 | ret = mIat.startListening(mRecognizerListener); 189 | 190 | if (ret != ErrorCode.SUCCESS) { 191 | showTip("识别失败,错误码:" + ret); 192 | } else { 193 | //iatFun();//讯飞demo里面的方法 194 | audioDecodeFun(audioPath); 195 | } 196 | 197 | break; 198 | } 199 | } 200 | 201 | /** 202 | * 讯飞 203 | */ 204 | private void iatFun(){ 205 | byte[] audioData = FucUtil.readAudioFile(MainActivity.this, "iattest.wav"); 206 | if (null != audioData) { 207 | showTip("开始识别"); 208 | // 一次(也可以分多次)写入音频文件数据,数据格式必须是采样率为8KHz或16KHz(本地识别只支持16K采样率,云端都支持),位长16bit,单声道的wav或者pcm 209 | // 写入8KHz采样的音频时,必须先调用setParameter(SpeechConstant.SAMPLE_RATE, "8000")设置正确的采样率 210 | // 注:当音频过长,静音部分时长超过VAD_EOS将导致静音后面部分不能识别。 211 | // 音频切分方法:FucUtil.splitBuffer(byte[] buffer,int length,int spsize); 212 | mIat.writeAudio(audioData, 0, audioData.length); 213 | mIat.stopListening(); 214 | } else { 215 | mIat.cancel(); 216 | showTip("读取音频流失败"); 217 | } 218 | } 219 | 220 | /** 221 | * 工具类 222 | * @param audioPath 223 | */ 224 | private void audioDecodeFun(String audioPath){ 225 | audioDecode = AudioDecode.newInstance(); 226 | audioDecode.setFilePath(audioPath); 227 | audioDecode.prepare(); 228 | audioDecode.setOnCompleteListener(new AudioDecode.OnCompleteListener() { 229 | @Override 230 | public void completed(final ArrayList pcmData) { 231 | if(pcmData!=null){ 232 | //写入音频文件数据,数据格式必须是采样率为8KHz或16KHz(本地识别只支持16K采样率,云端都支持),位长16bit,单声道的wav或者pcm 233 | //必须要先保存到本地,才能被讯飞识别 234 | //为防止数据较长,多次写入,把一次写入的音频,限制到 64K 以下,然后循环的调用wirteAudio,直到把音频写完为止 235 | for (byte[] data : pcmData){ 236 | mIat.writeAudio(data, 0, data.length); 237 | } 238 | Log.d("-----------stop",System.currentTimeMillis()+""); 239 | mIat.stopListening(); 240 | }else{ 241 | mIat.cancel(); 242 | Log.d(TAG,"--->读取音频流失败"); 243 | } 244 | audioDecode.release(); 245 | } 246 | }); 247 | audioDecode.startAsync(); 248 | } 249 | 250 | //上传用户词表,用于在听写着上传个性化数据,以提高匹配率 251 | private void uploadUserWords(){ 252 | String content = FucUtil.readFile(MainActivity.this,"userwords.txt","utf-8"); 253 | et_voice_text.setText(content); 254 | 255 | ret = mIat.updateLexicon("userword", content, mLexiconListener); 256 | if (ret != ErrorCode.SUCCESS){ 257 | Log.d(TAG,"上传热词失败,错误码:" + ret); 258 | } 259 | } 260 | 261 | private void showTip(final String str) { 262 | mToast.setText(str); 263 | mToast.show(); 264 | } 265 | 266 | /** 267 | * 参数设置 268 | */ 269 | private void setParam(){ 270 | //参数设置 271 | /** 272 | * 应用领域 服务器为不同的应用领域,定制了不同的听写匹配引擎,使用对应的领域能获取更 高的匹配率 273 | * 应用领域用于听写和语音语义服务。当前支持的应用领域有: 274 | * 短信和日常用语:iat (默认) 275 | * 视频:video 276 | * 地图:poi 277 | * 音乐:music 278 | */ 279 | mIat.setParameter(SpeechConstant.DOMAIN,"iat"); 280 | /** 281 | * 在听写和语音语义理解时,可通过设置此参数,选择要使用的语言区域 282 | * 当前支持: 283 | * 简体中文:zh_cn(默认) 284 | * 美式英文:en_us 285 | */ 286 | mIat.setParameter(SpeechConstant.LANGUAGE,"zh_cn"); 287 | /** 288 | * 每一种语言区域,一般还有不同的方言,通过此参数,在听写和语音语义理解时, 设置不同的方言参数。 289 | * 当前仅在LANGUAGE为简体中文时,支持方言选择,其他语言区域时, 请把此参数值设为null。 290 | * 普通话:mandarin(默认) 291 | * 粤 语:cantonese 292 | * 四川话:lmz 293 | * 河南话:henanese 294 | */ 295 | mIat.setParameter(SpeechConstant.ACCENT,"mandarin"); 296 | // 设置听写引擎 297 | mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType); 298 | //设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理 299 | //默认值:短信转写5000,其他4000 300 | mIat.setParameter(SpeechConstant.VAD_BOS,"4000"); 301 | // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音 302 | mIat.setParameter(SpeechConstant.VAD_EOS,"1000"); 303 | // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点 304 | mIat.setParameter(SpeechConstant.ASR_PTT,"1"); 305 | // 设置音频保存路径,保存音频格式支持pcm、wav 306 | mIat.setParameter(SpeechConstant.AUDIO_FORMAT,"wav"); 307 | //mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/iat.wav"); 308 | //文本,编码 309 | mIat.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8"); 310 | } 311 | 312 | @Override 313 | protected void onDestroy() { 314 | super.onDestroy(); 315 | // 退出时释放连接 316 | mIat.cancel(); 317 | //mIat.destroy(); 318 | } 319 | @Override 320 | protected void onResume() { 321 | // 开放统计 移动数据统计分析 322 | FlowerCollector.onResume(MainActivity.this); 323 | FlowerCollector.onPageStart(TAG); 324 | super.onResume(); 325 | } 326 | 327 | @Override 328 | protected void onPause() { 329 | // 开放统计 移动数据统计分析 330 | FlowerCollector.onPageEnd(TAG); 331 | FlowerCollector.onPause(MainActivity.this); 332 | super.onPause(); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /app/src/main/java/com/alvin/speechdemo/util/AudioDecode.java: -------------------------------------------------------------------------------- 1 | package com.alvin.speechdemo.util; 2 | 3 | 4 | import android.media.AudioFormat; 5 | import android.media.MediaCodec; 6 | import android.media.MediaExtractor; 7 | import android.media.MediaFormat; 8 | import android.os.Build; 9 | import android.util.Log; 10 | 11 | import java.io.IOException; 12 | import java.nio.ByteBuffer; 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * 本地AMR录音解码成PCM数据流 17 | */ 18 | public class AudioDecode { 19 | 20 | private static final String TAG = "AudioDecode"; 21 | private String srcPath;//语音本地路径 22 | private MediaCodec mediaDecode; 23 | private MediaExtractor mediaExtractor; 24 | private ByteBuffer[] decodeInputBuffers; 25 | private ByteBuffer[] decodeOutputBuffers; 26 | private MediaCodec.BufferInfo decodeBufferInfo; 27 | private ArrayList chunkPCMDataContainer;//PCM数据块容器 28 | private OnCompleteListener onCompleteListener; 29 | private boolean codeOver = false;//解码结束 30 | private byte[] pcmData;//解码最终存储的完整数据流 31 | private Thread decoderThread; 32 | 33 | public static AudioDecode newInstance() { 34 | return new AudioDecode(); 35 | } 36 | 37 | /** 38 | * 设置要读取的文件位置 39 | * 40 | * @param srcPath 41 | */ 42 | public void setFilePath(String srcPath) { 43 | this.srcPath = srcPath; 44 | } 45 | 46 | /** 47 | * 准备工作 48 | */ 49 | public void prepare() { 50 | 51 | if (srcPath == null) { 52 | throw new IllegalArgumentException("srcPath can't be null"); 53 | } 54 | chunkPCMDataContainer = new ArrayList<>(); 55 | initMediaDecode();//解码器 56 | } 57 | 58 | /** 59 | * 初始化解码器 60 | */ 61 | private void initMediaDecode() { 62 | try { 63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 64 | mediaExtractor = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道 65 | mediaExtractor.setDataSource(srcPath);//媒体文件的位置 66 | for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道 67 | MediaFormat format = mediaExtractor.getTrackFormat(i);; 68 | format.setInteger(MediaFormat.KEY_BIT_RATE, AudioFormat.ENCODING_PCM_16BIT); 69 | String mime = format.getString(MediaFormat.KEY_MIME); 70 | if (mime.startsWith("audio")) {//获取音频轨道 71 | mediaExtractor.selectTrack(i);//选择此音频轨道 72 | mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器 73 | mediaDecode.configure(format, null, null, 0); 74 | break; 75 | } 76 | } 77 | if (mediaDecode == null) { 78 | Log.e(TAG, "create mediaDecode failed"); 79 | return; 80 | } 81 | mediaDecode.start();//启动MediaCodec ,等待传入数据 82 | decodeInputBuffers = mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据 83 | decodeOutputBuffers = mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据 84 | decodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息 85 | showLog("buffers:" + decodeInputBuffers.length); 86 | } 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | 93 | 94 | /** 95 | * 开始转码 96 | * 音频数据 解码成PCM,获取到PCM数组 97 | */ 98 | public void startAsync() { 99 | decoderThread = new Thread(new DecodeRunnable()); 100 | decoderThread.start(); 101 | } 102 | 103 | /** 104 | * 将PCM数据存入{@link #chunkPCMDataContainer} 105 | * 106 | * @param pcmChunk PCM数据块 107 | */ 108 | private void putPCMData(byte[] pcmChunk) { 109 | synchronized (AudioDecode.class) {//记得加锁 110 | chunkPCMDataContainer.add(pcmChunk); 111 | } 112 | } 113 | 114 | /** 115 | * 在Container中{@link #chunkPCMDataContainer}取出PCM数据 116 | * 117 | * @return PCM数据块 118 | */ 119 | private byte[] getPCMData() { 120 | synchronized (AudioDecode.class) {//记得加锁 121 | if (chunkPCMDataContainer.isEmpty()) { 122 | return null; 123 | } 124 | 125 | byte[] pcmChunk = chunkPCMDataContainer.get(0);//每次取出index 0 的数据 126 | chunkPCMDataContainer.remove(pcmChunk);//取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存 127 | return pcmChunk; 128 | } 129 | } 130 | 131 | 132 | /** 133 | * 解码{@link #srcPath}音频文件 得到PCM数据块 134 | * 135 | * @return 是否解码完所有数据 136 | */ 137 | private void srcAudioFormatToPCM() { 138 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 139 | if(decodeInputBuffers!=null){ 140 | try { 141 | for (int i = 0; i < decodeInputBuffers.length - 1; i++) { 142 | int inputIndex = 0;//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧 143 | inputIndex = mediaDecode.dequeueInputBuffer(-1); 144 | if (inputIndex < 0) { 145 | codeOver = true; 146 | return; 147 | } 148 | ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer 149 | inputBuffer.clear();//清空之前传入inputBuffer内的数据 150 | int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中 151 | if (sampleSize < 0) {//小于0 代表所有数据已读取完成 152 | codeOver = true; 153 | } else { 154 | mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据 155 | mediaExtractor.advance();//MediaExtractor移动到下一取样处 156 | } 157 | } 158 | 159 | //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒 160 | //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待 161 | int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000); 162 | 163 | ByteBuffer outputBuffer; 164 | byte[] chunkPCM; 165 | while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据 166 | outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer 167 | chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小 168 | outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中 169 | outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 170 | putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码 171 | mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据 172 | outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束 173 | } 174 | 175 | if(codeOver){ 176 | if (onCompleteListener != null) { 177 | onCompleteListener.completed(chunkPCMDataContainer); 178 | } 179 | } 180 | }catch (Exception e){ 181 | e.printStackTrace(); 182 | codeOver = true; 183 | if (onCompleteListener != null) { 184 | onCompleteListener.completed(chunkPCMDataContainer); 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * android 5.0以上 193 | */ 194 | private void srcAudioFormatToPCMHigherApi() { 195 | if (Build.VERSION.SDK_INT >= 21){ 196 | boolean sawOutputEOS = false; 197 | final long kTimeOutUs = 10000; 198 | long presentationTimeUs = 0; 199 | while (!sawOutputEOS){ 200 | try{ 201 | int inputIndex = mediaDecode.dequeueInputBuffer(-1); 202 | if (inputIndex >= 0){ 203 | ByteBuffer inputBuffer = mediaDecode.getInputBuffer(inputIndex); 204 | if(inputBuffer!=null){ 205 | int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0); 206 | if (sampleSize < 0) {// 小于0 代表所有数据已读取完成 207 | sawOutputEOS = true; 208 | codeOver = true; 209 | break; 210 | }else{ 211 | presentationTimeUs = mediaExtractor.getSampleTime(); 212 | mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, presentationTimeUs, 0);// 通知MediaDecode解码刚刚传入的数据 213 | mediaExtractor.advance(); 214 | } 215 | } 216 | }else{ 217 | sawOutputEOS = true; 218 | codeOver = true; 219 | } 220 | int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, kTimeOutUs); 221 | ByteBuffer outputBuffer ;//= mediaDecode.getOutputBuffer(outputIndex);// 拿到用于存放PCM数据的Buffer 222 | while (outputIndex >= 0){ 223 | outputBuffer = mediaDecode.getOutputBuffer(outputIndex); 224 | boolean doRender = (decodeBufferInfo.size != 0); 225 | if(doRender && outputBuffer!=null){ 226 | outputBuffer.position(decodeBufferInfo.offset); 227 | outputBuffer.limit(decodeBufferInfo.offset + decodeBufferInfo.size); 228 | byte[] chunkPCM = new byte[decodeBufferInfo.size];// BufferInfo内定义了此数据块的大小 229 | outputBuffer.get(chunkPCM); 230 | outputBuffer.clear();// 数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 231 | putPCMData(chunkPCM);// 自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码 232 | mediaDecode.releaseOutputBuffer(outputIndex, false);// 此操作一定要做,不然MediaCodec用完所有的Buffer后将不能向外输出数据 233 | outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, kTimeOutUs); 234 | } 235 | } 236 | }catch (Exception e){ 237 | e.printStackTrace(); 238 | sawOutputEOS = true; 239 | codeOver = true; 240 | } 241 | } 242 | if(codeOver){ 243 | if (onCompleteListener != null) { 244 | onCompleteListener.completed(chunkPCMDataContainer); 245 | } 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * 释放资源 252 | */ 253 | public void release() { 254 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){ 255 | 256 | try{ 257 | if(decoderThread!=null && decoderThread.isAlive()){ 258 | decoderThread.interrupt(); 259 | codeOver = true; 260 | } 261 | 262 | if (mediaDecode != null) { 263 | mediaDecode.stop(); 264 | mediaDecode.release(); 265 | mediaDecode = null; 266 | } 267 | 268 | if (mediaExtractor != null) { 269 | mediaExtractor.release(); 270 | mediaExtractor = null; 271 | } 272 | 273 | if (onCompleteListener != null) { 274 | onCompleteListener = null; 275 | } 276 | }catch (Exception e){ 277 | e.printStackTrace(); 278 | } 279 | 280 | } 281 | } 282 | 283 | /** 284 | * 解码线程 285 | */ 286 | private class DecodeRunnable implements Runnable { 287 | 288 | @Override 289 | public void run() { 290 | while (!codeOver) { 291 | if(Build.VERSION.SDK_INT>=21){ 292 | srcAudioFormatToPCMHigherApi(); 293 | }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){ 294 | srcAudioFormatToPCM(); 295 | } 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * 解码完成回调接口 302 | */ 303 | public interface OnCompleteListener { 304 | void completed(ArrayList chunkPCMDataContainer); 305 | } 306 | 307 | /** 308 | * 设置转码完成监听器 309 | * @param onCompleteListener 监听器 310 | */ 311 | public void setOnCompleteListener(OnCompleteListener onCompleteListener) { 312 | this.onCompleteListener = onCompleteListener; 313 | } 314 | 315 | private void showLog(String msg) { 316 | Log.e("AudioCodec", msg); 317 | } 318 | } 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /app/src/main/java/com/alvin/speechdemo/util/FucUtil.java: -------------------------------------------------------------------------------- 1 | package com.alvin.speechdemo.util; 2 | 3 | import android.content.Context; 4 | import android.media.AudioFormat; 5 | import android.media.AudioManager; 6 | import android.media.AudioRecord; 7 | import android.media.MediaRecorder; 8 | 9 | import java.io.InputStream; 10 | 11 | /** 12 | * 功能性函数扩展类 13 | */ 14 | public class FucUtil { 15 | 16 | public static String readFile(Context mContext, String file, String code) { 17 | int len = 0; 18 | byte[] buf = null; 19 | String result = ""; 20 | try { 21 | InputStream in = mContext.getClass().getClassLoader().getResourceAsStream("assets/"+file); 22 | len = in.available(); 23 | buf = new byte[len]; 24 | in.read(buf, 0, len); 25 | 26 | result = new String(buf, code); 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | } 30 | return result; 31 | } 32 | 33 | /** 34 | * 读取asset目录下音频文件。 35 | * 36 | * @return 二进制文件数据 37 | */ 38 | public static byte[] readAudioFile(Context context, String filename) { 39 | try { 40 | InputStream ins = context.getAssets().open(filename); 41 | byte[] data = new byte[ins.available()]; 42 | 43 | ins.read(data); 44 | ins.close(); 45 | 46 | return data; 47 | } catch (Exception e) { 48 | // TODO Auto-generated catch block 49 | e.printStackTrace(); 50 | } 51 | 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/alvin/speechdemo/util/JsonParser.java: -------------------------------------------------------------------------------- 1 | package com.alvin.speechdemo.util; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | import org.json.JSONTokener; 6 | 7 | /** 8 | * Created by Administrator on 2016/10/18. 9 | */ 10 | 11 | public class JsonParser { 12 | public static String parseIatResult(String json){ 13 | StringBuilder ret = new StringBuilder(); 14 | try{ 15 | JSONTokener tokener = new JSONTokener(json); 16 | JSONObject jsonObject = new JSONObject(tokener); 17 | JSONArray words = jsonObject.getJSONArray("ws"); 18 | int len = words.length(); 19 | for (int i=0;i 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/push_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_view_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alvin3225/SpeechDemo/98584925e592f53a9846585fe3afc9fc09df5a7e/app/src/main/res/drawable-xhdpi/img_view_loading.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 21 | 22 | 26 | 27 | 32 | 33 | 41 | 42 | 53 | 54 |