接入讯飞SDK及参数设置

简要描述

主要协助云上越秀App接入讯飞SDK,并且设置讯飞相应参数的功能代码示例;

主要内容

版本号:1.1140

包大小:2.7M(jar包+so文件大小)

引入方式:jar包+so库,本地引入

代码设置

1、10s内无声音自动中断

核心代码段为 SpeechConstant.VAD_BOSSpeechConstant.VAD_EOS

  1. //kotlin实现
  2. SpeechRecognizer.createRecognizer(mContext, mTtsInitListener).apply {
  3. // 清空参数
  4. setParameter(SpeechConstant.PARAMS, null)
  5. // 设置听写引擎
  6. setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
  7. // 设置返回结果格式
  8. setParameter(SpeechConstant.RESULT_TYPE, "json")
  9. // * 在线听写支持多种小语种设置。支持语言类型如下:
  10. // * <item>zh_cn</item> 中文 默认
  11. // * <item>en_us</item> 英文
  12. // * <item>ja_jp</item> 日语
  13. // * <item>ru-ru</item> 俄语
  14. // * <item>es_es</item> 西班牙语
  15. // * <item>fr_fr</item> 法语
  16. // * <item>ko_kr</item> 韩语
  17. setParameter(SpeechConstant.LANGUAGE, "zh_cn")
  18. // 设置语言区域
  19. setParameter(SpeechConstant.ACCENT, "mandarin")
  20. // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
  21. setParameter(SpeechConstant.VAD_BOS, "10000")
  22. // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
  23. setParameter(SpeechConstant.VAD_EOS, "10000")
  24. // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
  25. setParameter(SpeechConstant.ASR_PTT, "1")
  26. // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
  27. setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
  28. }

2、监听讯飞回调
实现RecognizerListener接口(kotlin)

  1. fun startListening(
  2. filePath: (String) -> Unit,
  3. ready: () -> Unit,
  4. endSpeak: () -> Unit,
  5. error: (SpeechError?) -> Unit,
  6. result: (String?) -> Unit,
  7. finish: () -> Unit
  8. ): Int {
  9. val value = object : RecognizerListener {
  10. override fun onVolumeChanged(volume: Int, data: ByteArray?) {
  11. // SparringLog.d(TAG,"当前正在说话,音量大小:$volume")
  12. // SparringLog.d(TAG,"返回音频数据:" + data?.size)
  13. }
  14. override fun onResult(results: RecognizerResult?, isLast: Boolean) {
  15. val resultString = results?.resultString
  16. SparringLog.d(TAG, resultString.toString())
  17. result(resultString)
  18. if (isLast) {
  19. finish()
  20. }
  21. }
  22. override fun onBeginOfSpeech() {
  23. // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
  24. SparringLog.d(TAG, "开始说话")
  25. ready()
  26. }
  27. override fun onEvent(eventType: Int, arg1: Int, arg2: Int, obj: Bundle?) {
  28. // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
  29. // 若使用本地能力,会话id为null
  30. // if (SpeechEvent.EVENT_SESSION_ID == eventType) {
  31. // String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
  32. // Log.d(TAG, "session id =" + sid);
  33. // }
  34. }
  35. override fun onEndOfSpeech() {
  36. // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
  37. SparringLog.d(TAG, "结束说话")
  38. endSpeak()
  39. }
  40. override fun onError(error: SpeechError?) {
  41. // TODO: 处理此权限的错误 立即停止回调的也是10118 没有权限也是10118 怎么区分开?
  42. // 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
  43. SparringLog.e(TAG, error?.toString())
  44. error(error)
  45. finish()
  46. }
  47. }
  48. return mIat.run {
  49. val tempPath =
  50. com.yxt.sparring.utils.FileUtil.sInstance.PATH_APP_MEDIA +
  51. File.separator + UUID.randomUUID().toString() + ".pcm"
  52. filePath(tempPath)
  53. setParameter(SpeechConstant.ASR_AUDIO_PATH, tempPath)
  54. startListening(value)
  55. }
  56. }

以下是我们封装的讯飞工具类,供参考
XunFeiUtil

  1. import android.content.Context
  2. import android.os.Bundle
  3. import android.os.Environment
  4. import android.os.MemoryFile
  5. import com.iflytek.cloud.*
  6. import com.iflytek.cloud.msc.util.FileUtil
  7. import com.yxt.sparring.utils.common.log.SparringLog
  8. import java.io.File
  9. import java.io.IOException
  10. import java.util.*
  11. class XunFeiUtil private constructor() {
  12. companion object {
  13. private var TAG = XunFeiUtil::class.java.simpleName
  14. @JvmStatic
  15. val instance: XunFeiUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
  16. XunFeiUtil()
  17. }
  18. }
  19. private lateinit var mContext: Context
  20. fun init(context: Context) {
  21. mContext = context
  22. SpeechUtility.createUtility(context, SpeechConstant.APPID + "=5ab0cdad")
  23. }
  24. fun startListening(
  25. filePath: (String) -> Unit,
  26. ready: () -> Unit,
  27. endSpeak: () -> Unit,
  28. error: (SpeechError?) -> Unit,
  29. result: (String?) -> Unit,
  30. finish: () -> Unit
  31. ): Int {
  32. val value = object : RecognizerListener {
  33. override fun onVolumeChanged(volume: Int, data: ByteArray?) {
  34. // SparringLog.d(TAG,"当前正在说话,音量大小:$volume")
  35. // SparringLog.d(TAG,"返回音频数据:" + data?.size)
  36. }
  37. override fun onResult(results: RecognizerResult?, isLast: Boolean) {
  38. val resultString = results?.resultString
  39. SparringLog.d(TAG, resultString.toString())
  40. result(resultString)
  41. if (isLast) {
  42. finish()
  43. }
  44. }
  45. override fun onBeginOfSpeech() {
  46. // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
  47. SparringLog.d(TAG, "开始说话")
  48. ready()
  49. }
  50. override fun onEvent(eventType: Int, arg1: Int, arg2: Int, obj: Bundle?) {
  51. // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
  52. // 若使用本地能力,会话id为null
  53. // if (SpeechEvent.EVENT_SESSION_ID == eventType) {
  54. // String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
  55. // Log.d(TAG, "session id =" + sid);
  56. // }
  57. }
  58. override fun onEndOfSpeech() {
  59. // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
  60. SparringLog.d(TAG, "结束说话")
  61. endSpeak()
  62. }
  63. override fun onError(error: SpeechError?) {
  64. // TODO: 处理此权限的错误 立即停止回调的也是10118 没有权限也是10118 怎么区分开?
  65. // 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
  66. SparringLog.e(TAG, error?.toString())
  67. error(error)
  68. finish()
  69. }
  70. }
  71. return mIat.run {
  72. val tempPath =
  73. com.yxt.sparring.utils.FileUtil.sInstance.PATH_APP_MEDIA +
  74. File.separator + UUID.randomUUID().toString() + ".pcm"
  75. filePath(tempPath)
  76. setParameter(SpeechConstant.ASR_AUDIO_PATH, tempPath)
  77. startListening(value)
  78. }
  79. }
  80. // 语音合成
  81. fun startSpeaking(name: String?, content: String, needDelay: (Boolean) -> Unit) {
  82. mTts.apply {
  83. // 后台api没有返回speaker的时候是否返回性别问题;旁白声音问题;
  84. // 已确认2020.5.9(开会小组)-----后端不返回speaker时,App端不需要转语音,跟旁白一样处理
  85. if (name.isNullOrBlank()) {
  86. needDelay(true)
  87. } else {
  88. setParameter(SpeechConstant.VOICE_NAME, name)
  89. startSpeaking(content, object : SynthesizerListener {
  90. val container = Vector<ByteArray?>()
  91. override fun onBufferProgress(percent: Int, beginPos: Int, endPos: Int, info: String?) {
  92. // 合成进度
  93. SparringLog.d(TAG, "缓冲进度为$percent")
  94. }
  95. override fun onSpeakBegin() {
  96. SparringLog.d(TAG, "开始播放")
  97. }
  98. override fun onSpeakProgress(percent: Int, beginPos: Int, endPos: Int) {
  99. SparringLog.d(TAG, "播放进度为$percent")
  100. }
  101. override fun onEvent(eventType: Int, arg1: Int, arg12: Int, obj: Bundle?) {
  102. // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
  103. // 若使用本地能力,会话id为null
  104. if (SpeechEvent.EVENT_SESSION_ID == eventType) {
  105. val sid: String? = obj?.getString(SpeechEvent.KEY_EVENT_SESSION_ID)
  106. SparringLog.d(TAG, "session id =$sid")
  107. }
  108. //当设置SpeechConstant.TTS_DATA_NOTIFY为1时,抛出buf数据
  109. if (SpeechEvent.EVENT_TTS_BUFFER == eventType) {
  110. val buf: ByteArray? = obj?.getByteArray(SpeechEvent.KEY_EVENT_TTS_BUFFER)
  111. SparringLog.d(TAG, "buf is =" + buf?.size)
  112. container.add(buf)
  113. }
  114. }
  115. override fun onSpeakPaused() {
  116. SparringLog.d(TAG, "暂停播放")
  117. }
  118. override fun onSpeakResumed() {
  119. SparringLog.d(TAG, "继续播放")
  120. }
  121. override fun onCompleted(error: SpeechError?) {
  122. if (error == null) {
  123. SparringLog.d(TAG, "播放完成," + container.size)
  124. needDelay.invoke(false)
  125. try {
  126. for (i in container.indices) {
  127. writeToFile(container[i])
  128. }
  129. } catch (e: IOException) {
  130. }
  131. FileUtil.saveFile(
  132. memFile,
  133. mTotalSize,
  134. Environment.getExternalStorageDirectory().toString() + "/real.pcm"
  135. )
  136. } else {
  137. needDelay.invoke(true)
  138. SparringLog.e(TAG, error.toString())
  139. }
  140. }
  141. })
  142. }
  143. }
  144. }
  145. fun stopListening() {
  146. if (mIat.isListening) {
  147. mIat.stopListening()
  148. }
  149. }
  150. fun stopSpeaking() {
  151. if (mTts.isSpeaking) {
  152. mTts.stopSpeaking()
  153. }
  154. }
  155. fun pauseSpeaking() {
  156. mTts.pauseSpeaking()
  157. }
  158. fun resumeSpeaking() {
  159. mTts.resumeSpeaking()
  160. }
  161. @Volatile
  162. private var mTotalSize: Long = 0
  163. private val memFile: MemoryFile by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
  164. val mFilepath = Environment.getExternalStorageDirectory().toString() + "/real.pcm"
  165. MemoryFile(mFilepath, 1920000)
  166. }
  167. @Throws(IOException::class)
  168. private fun writeToFile(data: ByteArray?) {
  169. if (data == null || data.isEmpty()) return
  170. try {
  171. memFile.allowPurging(false)
  172. memFile.writeBytes(data, 0, mTotalSize.toInt(), data.size)
  173. mTotalSize += data.size.toLong()
  174. } finally {
  175. }
  176. }
  177. private val mTts: SpeechSynthesizer by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
  178. SpeechSynthesizer.createSynthesizer(mContext, mTtsInitListener).apply {
  179. // 清空参数
  180. setParameter(SpeechConstant.PARAMS, null)
  181. // 根据合成引擎设置相应参数 目前只使用了cloud
  182. setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
  183. //支持实时音频返回,仅在synthesizeToUri条件下支持
  184. setParameter(SpeechConstant.TTS_DATA_NOTIFY, "0")
  185. // setParameter(SpeechConstant.TTS_BUFFER_TIME,"1");
  186. // 设置在线合成发音人
  187. // setParameter(SpeechConstant.VOICE_NAME, SPUtil.getString(SpeechConstant.VOICE_NAME, "xiaofeng"))
  188. //设置合成语速
  189. setParameter(SpeechConstant.SPEED, "50")
  190. //设置合成音调
  191. setParameter(SpeechConstant.PITCH, "50")
  192. //设置合成音量
  193. setParameter(SpeechConstant.VOLUME, "50")
  194. //设置播放器音频流类型 参见demo 通话/系统/铃声/*音乐*/闹铃/通知
  195. setParameter(SpeechConstant.STREAM_TYPE, "3")
  196. // 设置播放合成音频打断音乐播放,默认为true
  197. // setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true")
  198. // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
  199. setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
  200. setParameter(
  201. SpeechConstant.TTS_AUDIO_PATH,
  202. Environment.getExternalStorageDirectory().toString() + "/msc/tts.pcm"
  203. )
  204. }
  205. }
  206. private val mTtsInitListener: (Int) -> Unit = { code ->
  207. if (code != ErrorCode.SUCCESS) {
  208. SparringLog.e(
  209. TAG,
  210. "初始化失败,错误码:$code,请点击网址https://www.xfyun.cn/document/error-code查询解决方案"
  211. )
  212. }
  213. }
  214. // 在线听写
  215. private val mIat: SpeechRecognizer by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
  216. SpeechRecognizer.createRecognizer(mContext, mTtsInitListener).apply {
  217. // 清空参数
  218. setParameter(SpeechConstant.PARAMS, null)
  219. // 设置听写引擎
  220. setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
  221. // 设置返回结果格式
  222. setParameter(SpeechConstant.RESULT_TYPE, "json")
  223. // * 在线听写支持多种小语种设置。支持语言类型如下:
  224. // * <item>zh_cn</item> 中文 默认
  225. // * <item>en_us</item> 英文
  226. // * <item>ja_jp</item> 日语
  227. // * <item>ru-ru</item> 俄语
  228. // * <item>es_es</item> 西班牙语
  229. // * <item>fr_fr</item> 法语
  230. // * <item>ko_kr</item> 韩语
  231. setParameter(SpeechConstant.LANGUAGE, "zh_cn")
  232. // 设置语言区域
  233. setParameter(SpeechConstant.ACCENT, "mandarin")
  234. // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
  235. setParameter(SpeechConstant.VAD_BOS, "10000")
  236. // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
  237. setParameter(SpeechConstant.VAD_EOS, "10000")
  238. // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
  239. setParameter(SpeechConstant.ASR_PTT, "1")
  240. // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
  241. setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
  242. // setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory().toString() + "/pcm/2.pcm")
  243. }
  244. }
  245. fun stop() {
  246. stopListening()
  247. // 退出时释放连接
  248. mIat.cancel()
  249. mIat.destroy()
  250. stopSpeaking()
  251. mTts.destroy()
  252. }
  253. }

IatBean

  1. data class IatBean(
  2. val bg: Int,
  3. val ed: Int,
  4. val ls: Boolean,
  5. val sn: Int,
  6. val ws: List<W>
  7. ) {
  8. val plainText :String?
  9. get() {
  10. val buffer = StringBuffer()
  11. for (w in ws) {
  12. // 转写结果词,默认使用第一个结果
  13. // 如果需要多候选结果,解析数组其他字段
  14. // for(int j = 0; j < items.length(); j++)
  15. // {
  16. // JSONObject obj = items.getJSONObject(j);
  17. // ret.append(obj.getString("w"));
  18. // }
  19. buffer.append(w.cw[0].w)
  20. }
  21. return buffer.toString()
  22. }
  23. }
  24. data class W(
  25. val bg: Int,
  26. val cw: List<Cw>
  27. )
  28. data class Cw(
  29. val sc: Double,
  30. val w: String
  31. )