接入讯飞SDK及参数设置
简要描述
主要协助云上越秀App接入讯飞SDK,并且设置讯飞相应参数的功能代码示例;
主要内容
版本号:1.1140
包大小:2.7M
(jar包+so文件大小)
引入方式:jar包+so库,本地引入
代码设置
1、10s内无声音自动中断
核心代码段为 SpeechConstant.VAD_BOS
和 SpeechConstant.VAD_EOS
//kotlin实现
SpeechRecognizer.createRecognizer(mContext, mTtsInitListener).apply {
// 清空参数
setParameter(SpeechConstant.PARAMS, null)
// 设置听写引擎
setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
// 设置返回结果格式
setParameter(SpeechConstant.RESULT_TYPE, "json")
// * 在线听写支持多种小语种设置。支持语言类型如下:
// * <item>zh_cn</item> 中文 默认
// * <item>en_us</item> 英文
// * <item>ja_jp</item> 日语
// * <item>ru-ru</item> 俄语
// * <item>es_es</item> 西班牙语
// * <item>fr_fr</item> 法语
// * <item>ko_kr</item> 韩语
setParameter(SpeechConstant.LANGUAGE, "zh_cn")
// 设置语言区域
setParameter(SpeechConstant.ACCENT, "mandarin")
// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
setParameter(SpeechConstant.VAD_BOS, "10000")
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
setParameter(SpeechConstant.VAD_EOS, "10000")
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
setParameter(SpeechConstant.ASR_PTT, "1")
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
}
2、监听讯飞回调
实现RecognizerListener
接口(kotlin)
fun startListening(
filePath: (String) -> Unit,
ready: () -> Unit,
endSpeak: () -> Unit,
error: (SpeechError?) -> Unit,
result: (String?) -> Unit,
finish: () -> Unit
): Int {
val value = object : RecognizerListener {
override fun onVolumeChanged(volume: Int, data: ByteArray?) {
// SparringLog.d(TAG,"当前正在说话,音量大小:$volume")
// SparringLog.d(TAG,"返回音频数据:" + data?.size)
}
override fun onResult(results: RecognizerResult?, isLast: Boolean) {
val resultString = results?.resultString
SparringLog.d(TAG, resultString.toString())
result(resultString)
if (isLast) {
finish()
}
}
override fun onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
SparringLog.d(TAG, "开始说话")
ready()
}
override fun onEvent(eventType: Int, arg1: Int, arg2: Int, obj: Bundle?) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
override fun onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
SparringLog.d(TAG, "结束说话")
endSpeak()
}
override fun onError(error: SpeechError?) {
// TODO: 处理此权限的错误 立即停止回调的也是10118 没有权限也是10118 怎么区分开?
// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
SparringLog.e(TAG, error?.toString())
error(error)
finish()
}
}
return mIat.run {
val tempPath =
com.yxt.sparring.utils.FileUtil.sInstance.PATH_APP_MEDIA +
File.separator + UUID.randomUUID().toString() + ".pcm"
filePath(tempPath)
setParameter(SpeechConstant.ASR_AUDIO_PATH, tempPath)
startListening(value)
}
}
以下是我们封装的讯飞工具类,供参考
XunFeiUtil
import android.content.Context
import android.os.Bundle
import android.os.Environment
import android.os.MemoryFile
import com.iflytek.cloud.*
import com.iflytek.cloud.msc.util.FileUtil
import com.yxt.sparring.utils.common.log.SparringLog
import java.io.File
import java.io.IOException
import java.util.*
class XunFeiUtil private constructor() {
companion object {
private var TAG = XunFeiUtil::class.java.simpleName
@JvmStatic
val instance: XunFeiUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
XunFeiUtil()
}
}
private lateinit var mContext: Context
fun init(context: Context) {
mContext = context
SpeechUtility.createUtility(context, SpeechConstant.APPID + "=5ab0cdad")
}
fun startListening(
filePath: (String) -> Unit,
ready: () -> Unit,
endSpeak: () -> Unit,
error: (SpeechError?) -> Unit,
result: (String?) -> Unit,
finish: () -> Unit
): Int {
val value = object : RecognizerListener {
override fun onVolumeChanged(volume: Int, data: ByteArray?) {
// SparringLog.d(TAG,"当前正在说话,音量大小:$volume")
// SparringLog.d(TAG,"返回音频数据:" + data?.size)
}
override fun onResult(results: RecognizerResult?, isLast: Boolean) {
val resultString = results?.resultString
SparringLog.d(TAG, resultString.toString())
result(resultString)
if (isLast) {
finish()
}
}
override fun onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
SparringLog.d(TAG, "开始说话")
ready()
}
override fun onEvent(eventType: Int, arg1: Int, arg2: Int, obj: Bundle?) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
override fun onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
SparringLog.d(TAG, "结束说话")
endSpeak()
}
override fun onError(error: SpeechError?) {
// TODO: 处理此权限的错误 立即停止回调的也是10118 没有权限也是10118 怎么区分开?
// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
SparringLog.e(TAG, error?.toString())
error(error)
finish()
}
}
return mIat.run {
val tempPath =
com.yxt.sparring.utils.FileUtil.sInstance.PATH_APP_MEDIA +
File.separator + UUID.randomUUID().toString() + ".pcm"
filePath(tempPath)
setParameter(SpeechConstant.ASR_AUDIO_PATH, tempPath)
startListening(value)
}
}
// 语音合成
fun startSpeaking(name: String?, content: String, needDelay: (Boolean) -> Unit) {
mTts.apply {
// 后台api没有返回speaker的时候是否返回性别问题;旁白声音问题;
// 已确认2020.5.9(开会小组)-----后端不返回speaker时,App端不需要转语音,跟旁白一样处理
if (name.isNullOrBlank()) {
needDelay(true)
} else {
setParameter(SpeechConstant.VOICE_NAME, name)
startSpeaking(content, object : SynthesizerListener {
val container = Vector<ByteArray?>()
override fun onBufferProgress(percent: Int, beginPos: Int, endPos: Int, info: String?) {
// 合成进度
SparringLog.d(TAG, "缓冲进度为$percent")
}
override fun onSpeakBegin() {
SparringLog.d(TAG, "开始播放")
}
override fun onSpeakProgress(percent: Int, beginPos: Int, endPos: Int) {
SparringLog.d(TAG, "播放进度为$percent")
}
override fun onEvent(eventType: Int, arg1: Int, arg12: Int, obj: Bundle?) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
if (SpeechEvent.EVENT_SESSION_ID == eventType) {
val sid: String? = obj?.getString(SpeechEvent.KEY_EVENT_SESSION_ID)
SparringLog.d(TAG, "session id =$sid")
}
//当设置SpeechConstant.TTS_DATA_NOTIFY为1时,抛出buf数据
if (SpeechEvent.EVENT_TTS_BUFFER == eventType) {
val buf: ByteArray? = obj?.getByteArray(SpeechEvent.KEY_EVENT_TTS_BUFFER)
SparringLog.d(TAG, "buf is =" + buf?.size)
container.add(buf)
}
}
override fun onSpeakPaused() {
SparringLog.d(TAG, "暂停播放")
}
override fun onSpeakResumed() {
SparringLog.d(TAG, "继续播放")
}
override fun onCompleted(error: SpeechError?) {
if (error == null) {
SparringLog.d(TAG, "播放完成," + container.size)
needDelay.invoke(false)
try {
for (i in container.indices) {
writeToFile(container[i])
}
} catch (e: IOException) {
}
FileUtil.saveFile(
memFile,
mTotalSize,
Environment.getExternalStorageDirectory().toString() + "/real.pcm"
)
} else {
needDelay.invoke(true)
SparringLog.e(TAG, error.toString())
}
}
})
}
}
}
fun stopListening() {
if (mIat.isListening) {
mIat.stopListening()
}
}
fun stopSpeaking() {
if (mTts.isSpeaking) {
mTts.stopSpeaking()
}
}
fun pauseSpeaking() {
mTts.pauseSpeaking()
}
fun resumeSpeaking() {
mTts.resumeSpeaking()
}
@Volatile
private var mTotalSize: Long = 0
private val memFile: MemoryFile by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
val mFilepath = Environment.getExternalStorageDirectory().toString() + "/real.pcm"
MemoryFile(mFilepath, 1920000)
}
@Throws(IOException::class)
private fun writeToFile(data: ByteArray?) {
if (data == null || data.isEmpty()) return
try {
memFile.allowPurging(false)
memFile.writeBytes(data, 0, mTotalSize.toInt(), data.size)
mTotalSize += data.size.toLong()
} finally {
}
}
private val mTts: SpeechSynthesizer by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SpeechSynthesizer.createSynthesizer(mContext, mTtsInitListener).apply {
// 清空参数
setParameter(SpeechConstant.PARAMS, null)
// 根据合成引擎设置相应参数 目前只使用了cloud
setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
//支持实时音频返回,仅在synthesizeToUri条件下支持
setParameter(SpeechConstant.TTS_DATA_NOTIFY, "0")
// setParameter(SpeechConstant.TTS_BUFFER_TIME,"1");
// 设置在线合成发音人
// setParameter(SpeechConstant.VOICE_NAME, SPUtil.getString(SpeechConstant.VOICE_NAME, "xiaofeng"))
//设置合成语速
setParameter(SpeechConstant.SPEED, "50")
//设置合成音调
setParameter(SpeechConstant.PITCH, "50")
//设置合成音量
setParameter(SpeechConstant.VOLUME, "50")
//设置播放器音频流类型 参见demo 通话/系统/铃声/*音乐*/闹铃/通知
setParameter(SpeechConstant.STREAM_TYPE, "3")
// 设置播放合成音频打断音乐播放,默认为true
// setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true")
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
setParameter(
SpeechConstant.TTS_AUDIO_PATH,
Environment.getExternalStorageDirectory().toString() + "/msc/tts.pcm"
)
}
}
private val mTtsInitListener: (Int) -> Unit = { code ->
if (code != ErrorCode.SUCCESS) {
SparringLog.e(
TAG,
"初始化失败,错误码:$code,请点击网址https://www.xfyun.cn/document/error-code查询解决方案"
)
}
}
// 在线听写
private val mIat: SpeechRecognizer by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SpeechRecognizer.createRecognizer(mContext, mTtsInitListener).apply {
// 清空参数
setParameter(SpeechConstant.PARAMS, null)
// 设置听写引擎
setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD)
// 设置返回结果格式
setParameter(SpeechConstant.RESULT_TYPE, "json")
// * 在线听写支持多种小语种设置。支持语言类型如下:
// * <item>zh_cn</item> 中文 默认
// * <item>en_us</item> 英文
// * <item>ja_jp</item> 日语
// * <item>ru-ru</item> 俄语
// * <item>es_es</item> 西班牙语
// * <item>fr_fr</item> 法语
// * <item>ko_kr</item> 韩语
setParameter(SpeechConstant.LANGUAGE, "zh_cn")
// 设置语言区域
setParameter(SpeechConstant.ACCENT, "mandarin")
// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
setParameter(SpeechConstant.VAD_BOS, "10000")
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
setParameter(SpeechConstant.VAD_EOS, "10000")
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
setParameter(SpeechConstant.ASR_PTT, "1")
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
setParameter(SpeechConstant.AUDIO_FORMAT, "pcm")
// setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory().toString() + "/pcm/2.pcm")
}
}
fun stop() {
stopListening()
// 退出时释放连接
mIat.cancel()
mIat.destroy()
stopSpeaking()
mTts.destroy()
}
}
IatBean
data class IatBean(
val bg: Int,
val ed: Int,
val ls: Boolean,
val sn: Int,
val ws: List<W>
) {
val plainText :String?
get() {
val buffer = StringBuffer()
for (w in ws) {
// 转写结果词,默认使用第一个结果
// 如果需要多候选结果,解析数组其他字段
// for(int j = 0; j < items.length(); j++)
// {
// JSONObject obj = items.getJSONObject(j);
// ret.append(obj.getString("w"));
// }
buffer.append(w.cw[0].w)
}
return buffer.toString()
}
}
data class W(
val bg: Int,
val cw: List<Cw>
)
data class Cw(
val sc: Double,
val w: String
)