Browse Source

添加讯飞语音识别

weicu
tanlongsheng 10 months ago
parent
commit
bf3a9ebc2d
  1. 209
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt

209
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt

@ -0,0 +1,209 @@
package com.yunqiinnovation.azure_speech
import android.content.Context
import android.util.Base64
import android.util.Log
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.UUID
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class XunFeiAsrHelper(private val context: Context) {
private val tag = "XunFeiAsrHelper"
private val appId = "43d87d22"
private val accessKeyId = "2bd01ce06ac35957fc0c082f9b6284ec"
private val audioEncode = "pcm_s16le"
private val lang = "autodialect"
private val sampleRate = "16000"
private val audioFrameSize = 1280
private val frameIntervalMs = 40
private val baseWsUrl = "wss://office-api-ast-dx.iflyaisol.com/ast/communicate/v1"
private val client = OkHttpClient.Builder().retryOnConnectionFailure(true).build()
private var webSocket: WebSocket? = null
private val isConnected = AtomicBoolean(false)
private val isSendingAudio = AtomicBoolean(false)
private var sessionId: String? = null
private var audioFileSize: Long = 0
private fun getAudioFileSize(audioPath: String): Long {
val file = File(audioPath)
return if (file.exists() && file.isFile) file.length() else 0L
}
private fun getUtcTime(): String {
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US)
sdf.timeZone = TimeZone.getTimeZone("GMT+8")
return sdf.format(Date())
}
private fun calculateSignature(params: Map<String, String>, accessKeySecret: String): String {
val baseStr = buildString {
var first = true
for ((key, value) in params.toSortedMap()) {
if (key == "signature") continue
if (value.isBlank()) continue
if (!first) append("&")
append(URLEncoder.encode(key, StandardCharsets.UTF_8.name()))
append("=")
append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()))
first = false
}
}
val mac = Mac.getInstance("HmacSHA1")
val keySpec = SecretKeySpec(accessKeySecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA1")
mac.init(keySpec)
val signBytes = mac.doFinal(baseStr.toByteArray(StandardCharsets.UTF_8))
return Base64.encodeToString(signBytes, Base64.NO_WRAP)
}
private fun buildParamsString(params: Map<String, String>): String {
val sb = StringBuilder()
var first = true
for ((key, value) in params) {
if (!first) sb.append("&")
sb.append(URLEncoder.encode(key, StandardCharsets.UTF_8.name()))
sb.append("=")
sb.append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()))
first = false
}
return sb.toString()
}
fun connect(appId: String, accessKeyId: String, accessKeySecret: String): Boolean {
return try {
val params = mutableMapOf<String, String>()
params["audio_encode"] = audioEncode
params["lang"] = lang
params["samplerate"] = sampleRate
params["accessKeyId"] = accessKeyId
params["appId"] = appId
params["uuid"] = UUID.randomUUID().toString().replace("-", "")
params["utc"] = getUtcTime()
params["signature"] = calculateSignature(params, accessKeySecret)
val fullUrl = "$baseWsUrl?${buildParamsString(params)}"
val request = Request.Builder().url(fullUrl).build()
val latch = CountDownLatch(1)
val listener = object : WebSocketListener() {
override fun onOpen(ws: WebSocket, response: okhttp3.Response) {
webSocket = ws
isConnected.set(true)
latch.countDown()
}
override fun onMessage(ws: WebSocket, text: String) {
try {
val json = JSONObject(text)
if (json.optString("msg_type") == "action") {
val data = json.optJSONObject("data")
val sid = data?.optString("sessionId")
if (!sid.isNullOrEmpty()) {
sessionId = sid
}
}
} catch (e: Exception) {
Log.d(tag, "onMessage parse error: ${e.message}")
}
}
override fun onClosed(ws: WebSocket, code: Int, reason: String) {
isConnected.set(false)
}
override fun onFailure(ws: WebSocket, t: Throwable, response: okhttp3.Response?) {
isConnected.set(false)
Log.e(tag, "WebSocket failure: ${t.message}")
}
}
client.newWebSocket(request, listener)
latch.await(15, TimeUnit.SECONDS)
isConnected.get()
} catch (e: Exception) {
Log.e(tag, "connect failed: ${e.message}")
false
}
}
fun sendAudio(audioPath: String): Boolean {
if (!isConnected.get() || webSocket == null || isSendingAudio.get()) {
Log.d(tag, "sendAudio invalid state")
return false
}
isSendingAudio.set(true)
audioFileSize = getAudioFileSize(audioPath)
return try {
FileInputStream(audioPath).use { fis ->
val buffer = ByteArray(audioFrameSize)
var frameIndex = 0
var startTime: Long? = null
while (true) {
val bytesRead = fis.read(buffer)
if (bytesRead == -1) break
val frameData = if (bytesRead == audioFrameSize) buffer else buffer.copyOf(bytesRead)
if (startTime == null) startTime = System.currentTimeMillis()
val expectedSendTime = startTime!! + frameIndex * frameIntervalMs
val now = System.currentTimeMillis()
val diff = expectedSendTime - now
if (diff > 1) Thread.sleep(diff)
webSocket?.send(ByteString.of(frameData, 0, frameData.size))
frameIndex++
}
val endMsg = JSONObject()
endMsg.put("end", true)
sessionId?.let { endMsg.put("sessionId", it) }
webSocket?.send(endMsg.toString())
true
}
} catch (e: Exception) {
Log.e(tag, "sendAudio error: ${e.message}")
false
} finally {
isSendingAudio.set(false)
}
}
fun runDemo(appId: String, accessKeyId: String, accessKeySecret: String, audioPath: String) {
try {
if (!connect(appId, accessKeyId, accessKeySecret)) return
if (!sendAudio(audioPath)) return
val estimatedDurationSec = (((audioFileSize / audioFrameSize) * frameIntervalMs) / 1000).toInt()
val waitTime = estimatedDurationSec + 5
for (i in 0..waitTime) {
if (!isConnected.get()) break
Thread.sleep(1000)
}
} catch (_: InterruptedException) {
} finally {
close()
}
}
fun close() {
try {
isConnected.set(false)
webSocket?.close(1000, "normal")
} catch (_: Exception) {
} finally {
webSocket = null
}
}
}
Loading…
Cancel
Save