Browse Source

修改ble通信

weicu
fdp 1 year ago
parent
commit
eab05419d6
  1. 20
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  2. 2
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  3. 8
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  4. 21
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  5. 2
      local_plugins/ble_service/android/build.gradle.kts
  6. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  7. 201
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  8. 5
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/ChatAgent.kt
  9. 16
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt
  10. 653
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

20
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -50,7 +50,7 @@ object AgentService : CoroutineScope {
private var openaiModel: String = "gpt-3.5-turbo" private var openaiModel: String = "gpt-3.5-turbo"
// Azure服务 // Azure服务
private var azureAsrHelper: AzureAsrHelper? = null var azureAsrHelper: AzureAsrHelper? = null
private var azureTtsHelper: AzureTtsHelper? = null private var azureTtsHelper: AzureTtsHelper? = null
// OpenAI服务 // OpenAI服务
@ -112,7 +112,7 @@ object AgentService : CoroutineScope {
* @param config 配置参数,包含所需的所有API密钥和设置 * @param config 配置参数,包含所需的所有API密钥和设置
* @return 是否初始化成功 * @return 是否初始化成功
*/ */
fun initialize(appContext: Context, config: Map<String, Any>): Boolean { fun initialize(appContext: Context, config: Map<String, Any>,isExternalActive:Boolean): Boolean {
if (isInitialized) return true if (isInitialized) return true
try { try {
@ -149,7 +149,8 @@ object AgentService : CoroutineScope {
initialize( initialize(
subscriptionKey = azureSpeechKey, subscriptionKey = azureSpeechKey,
region = azureSpeechRegion, region = azureSpeechRegion,
audioSourceType = AzureAsrHelper.AudioSourceType.MICROPHONE, audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL
else AzureAsrHelper.AudioSourceType.MICROPHONE
) )
} }
@ -277,7 +278,7 @@ object AgentService : CoroutineScope {
/** /**
* 开始语音识别 * 开始语音识别
*/ */
fun startRecognition(): Boolean { fun startRecognition(isExternalActive:Boolean): Boolean {
if (!isInitialized) { if (!isInitialized) {
FileLogger.e(TAG, "服务未初始化") FileLogger.e(TAG, "服务未初始化")
return false return false
@ -295,7 +296,14 @@ object AgentService : CoroutineScope {
isRecognitionActive = true isRecognitionActive = true
hasSpeechDetected = false hasSpeechDetected = false
try { try {
FileLogger.d(TAG, "选择音频源类型: ${isExternalActive}") //
// 选择音频源类型
val audioSourceType = if (isExternalActive) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) { override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) { if (recognizing.isNotEmpty()) {
@ -370,7 +378,7 @@ object AgentService : CoroutineScope {
"message" to error "message" to error
)) ))
} }
}) },audioSourceType)
return true return true
} catch (e: Exception) { } catch (e: Exception) {
isRecognitionActive = false isRecognitionActive = false

2
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -39,7 +39,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
"startConversation" -> { "startConversation" -> {
try { try {
val isStarted = AgentService.startRecognition() val isStarted = AgentService.startRecognition(false)
result.success(isStarted) result.success(isStarted)
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "启动对话失败", e) FileLogger.e(TAG, "启动对话失败", e)

8
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -254,12 +254,12 @@ class AzureAsrHelper(private val context: Context) {
* *
* @param callback 识别结果回调 * @param callback 识别结果回调
*/ */
fun recognizeOnce(callback: RecognizeCallback) { fun recognizeOnce(callback: RecognizeCallback, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE) {
if (speechConfig == null) { if (speechConfig == null) {
callback.onError("语音服务未初始化") callback.onError("语音服务未初始化")
return return
} }
this.audioSourceType = audioSourceType
// 确保不在连续识别中 // 确保不在连续识别中
if (isContinuousRecognitionActive) { if (isContinuousRecognitionActive) {
stopContinuousRecognition() stopContinuousRecognition()
@ -299,7 +299,7 @@ class AzureAsrHelper(private val context: Context) {
* @param callback 连续识别结果回调 * @param callback 连续识别结果回调
* @return 是否成功开始识别 * @return 是否成功开始识别
*/ */
fun startContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean { fun startContinuousRecognition(callback: ContinuousRecognizeCallback, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE): Boolean {
if (speechConfig == null) { if (speechConfig == null) {
callback.onError("语音服务未初始化") callback.onError("语音服务未初始化")
return false return false
@ -308,7 +308,7 @@ class AzureAsrHelper(private val context: Context) {
if (isContinuousRecognitionActive) { if (isContinuousRecognitionActive) {
return true return true
} }
this.audioSourceType = audioSourceType
// 重置识别器 // 重置识别器
if (!setupRecognizer()) { if (!setupRecognizer()) {
callback.onError("重置识别器失败") callback.onError("重置识别器失败")

21
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -123,6 +123,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try { try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型 // 选择音频源类型
val audioSourceType = if (useExternalAudio) { val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL AzureAsrHelper.AudioSourceType.EXTERNAL
@ -156,7 +157,15 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
} }
} }
"recognizeOnce" -> { "recognizeOnce" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try { try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
// 直接使用同步方式进行一次性识别 // 直接使用同步方式进行一次性识别
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback { azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) { override fun onResult(text: String, detectedLanguage: String) {
@ -170,12 +179,13 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
override fun onError(error: String) { override fun onError(error: String) {
result.error("RECOGNITION_ERROR", error, null) result.error("RECOGNITION_ERROR", error, null)
} }
}) },audioSourceType)
} catch (e: Exception) { } catch (e: Exception) {
result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null) result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null)
} }
} }
"startContinuousRecognition" -> { "startContinuousRecognition" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
// 确保事件通道已准备好 // 确保事件通道已准备好
if (asrEventSink == null) { if (asrEventSink == null) {
result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null) result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null)
@ -183,6 +193,13 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
} }
try { try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) { override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(mapOf( sendAsrEvent(mapOf(
@ -222,7 +239,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
"message" to error "message" to error
)) ))
} }
}) },audioSourceType)
result.success(success) result.success(success)
} catch (e: Exception) { } catch (e: Exception) {
result.error("START_RECOGNITION_ERROR", e.message, null) result.error("START_RECOGNITION_ERROR", e.message, null)

2
local_plugins/ble_service/android/build.gradle.kts

@ -35,4 +35,6 @@ dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha03") implementation("androidx.security:security-crypto:1.1.0-alpha03")
implementation(project(":deepvoice_utils")) implementation(project(":deepvoice_utils"))
implementation(project(":agent_service")) implementation(project(":agent_service"))
implementation(project(":azure_speech"))
implementation(project(":jl_opus"))
} }

2
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

@ -23,7 +23,7 @@ object BleConst {
val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000abc2-0000-1111-2222-123456789abc") val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000abc2-0000-1111-2222-123456789abc")
/** 客户端特征配置描述符UUID */ /** 客户端特征配置描述符UUID */
val CLIENT_CHAR_CONFIG_UUID: UUID = UUID.fromString("0000fd7b-0000-1000-8000-00805f9b34fb") val CLIENT_CHAR_CONFIG_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
/** BLE扫描Action */ /** BLE扫描Action */
const val ACTION_SCAN_RESULT = "com.yunqiinnovation.ble_service.ACTION_BLE_SCAN" const val ACTION_SCAN_RESULT = "com.yunqiinnovation.ble_service.ACTION_BLE_SCAN"

201
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -23,6 +23,7 @@ import androidx.core.content.ContextCompat
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import com.yunqiinnovation.jl_opus.JlOpusPlugin
/** /**
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 * BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能
@ -85,6 +86,8 @@ object BleService {
// 回调集合 - 使用线程安全的集合存储所有回调 // 回调集合 - 使用线程安全的集合存储所有回调
private val callbacks = CopyOnWriteArrayList<Callback>() private val callbacks = CopyOnWriteArrayList<Callback>()
// 获取插件实例
private val opusPlugin = JlOpusPlugin()
// 初始化状态 // 初始化状态
private var isInitialized = false private var isInitialized = false
@ -101,7 +104,8 @@ object BleService {
this.context = appContext.applicationContext this.context = appContext.applicationContext
// 初始化蓝牙管理器和适配器 // 初始化蓝牙管理器和适配器
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter bluetoothAdapter = bluetoothManager.adapter
bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
@ -242,7 +246,8 @@ object BleService {
// 直接上报每个扫描结果 // 直接上报每个扫描结果
try { try {
// 将单个扫描结果转换为需要的格式 // 将单个扫描结果转换为需要的格式
val uuids = result.scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList() val uuids =
result.scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList()
val deviceMap = mapOf( val deviceMap = mapOf(
"id" to dev.address, "id" to dev.address,
"name" to (dev.name ?: "未知"), "name" to (dev.name ?: "未知"),
@ -273,10 +278,13 @@ object BleService {
private fun printDeviceInfo(result: ScanResult) { private fun printDeviceInfo(result: ScanResult) {
val device = result.device val device = result.device
val scanRecord = result.scanRecord val scanRecord = result.scanRecord
val serviceUuids = scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无" val serviceUuids =
scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无"
FileLogger.d(TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + FileLogger.d(
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]") TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " +
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]"
)
// 打印广播数据的详细信息 // 打印广播数据的详细信息
val scanData = scanRecord?.bytes val scanData = scanRecord?.bytes
@ -372,10 +380,12 @@ object BleService {
updateConnectionState(BleConst.STATE_CONNECTED) updateConnectionState(BleConst.STATE_CONNECTED)
g.discoverServices() g.discoverServices()
} }
newState == BluetoothProfile.STATE_DISCONNECTED -> { newState == BluetoothProfile.STATE_DISCONNECTED -> {
updateConnectionState(BleConst.STATE_DISCONNECTED) updateConnectionState(BleConst.STATE_DISCONNECTED)
disconnectGatt() disconnectGatt()
} }
else -> { else -> {
updateConnectionState(BleConst.STATE_ERROR) updateConnectionState(BleConst.STATE_ERROR)
disconnectGatt() disconnectGatt()
@ -391,6 +401,7 @@ object BleService {
// 获取主服务特征 // 获取主服务特征
val primarySvc = g.getService(BleConst.PRIMARY_SERVICE_UUID) val primarySvc = g.getService(BleConst.PRIMARY_SERVICE_UUID)
notifyChar = primarySvc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID) notifyChar = primarySvc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID)
writeChar = primarySvc?.getCharacteristic(BleConst.WRITE_CHAR_UUID) writeChar = primarySvc?.getCharacteristic(BleConst.WRITE_CHAR_UUID)
@ -405,10 +416,7 @@ object BleService {
} }
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
//要先设置音频服务的通知,否则接收不到
// 设置主服务的通知
setupNotifications(g, notifyChar)
// 设置音频服务的通知(如果存在) // 设置音频服务的通知(如果存在)
if (audioChar != null) { if (audioChar != null) {
setupNotifications(g, audioChar) setupNotifications(g, audioChar)
@ -416,11 +424,20 @@ object BleService {
} else { } else {
FileLogger.w(TAG, "音频服务特征未找到") FileLogger.w(TAG, "音频服务特征未找到")
} }
// 设置主服务的通知
setupNotifications(g, notifyChar)
//开启解码
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object :
JlOpusPlugin.CallbackOpus {
override fun onAudioDataReceivedOpus(pcmData: ByteArray) {
// 回调给上层
notifyAudioDataReceived(pcmData)
}
})
} }
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
FileLogger.d(TAG, "收到特征数据: ${c.uuid}, 数据=${c.value?.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}")
val data = c.value ?: return val data = c.value ?: return
// 根据特征UUID区分处理 // 根据特征UUID区分处理
@ -443,13 +460,23 @@ object BleService {
} }
// 其他未知数据 // 其他未知数据
else -> { else -> {
FileLogger.d(TAG, "收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}") FileLogger.d(
TAG,
"收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}"
)
} }
} }
} }
// 其他未知特征 // 其他未知特征
else -> { else -> {
FileLogger.d(TAG, "收到未知特征数据: ${c.uuid}, 数据=${data.joinToString(", ") { "0x%02X".format(it) }}") FileLogger.d(
TAG,
"收到未知特征数据: ${c.uuid}, 数据=${
data.joinToString(", ") {
"0x%02X".format(it)
}
}"
)
} }
} }
} }
@ -457,7 +484,10 @@ object BleService {
override fun onCharacteristicWrite( override fun onCharacteristicWrite(
g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int
) { ) {
FileLogger.i(TAG, "写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}") FileLogger.i(
TAG,
"写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}"
)
} }
override fun onDescriptorWrite( override fun onDescriptorWrite(
@ -489,11 +519,9 @@ object BleService {
*/ */
private fun processAudioData(data: ByteArray) { private fun processAudioData(data: ByteArray) {
try { try {
// 记录日志
FileLogger.d(TAG, "收到音频数据: ${data.size}字节")
// 回调给上层 opusPlugin?.handleDecodeRequest(data)
notifyAudioDataReceived(data)
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e) FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e)
} }
@ -503,7 +531,10 @@ object BleService {
* 处理设备响应数据 - 帧头为0xBB * 处理设备响应数据 - 帧头为0xBB
*/ */
private fun processDeviceResponse(data: ByteArray) { private fun processDeviceResponse(data: ByteArray) {
FileLogger.d(TAG, "收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}"
)
if (data.size < 3) { if (data.size < 3) {
FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短") FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短")
return return
@ -530,7 +561,7 @@ object BleService {
// 检查数据长度 // 检查数据长度
if (data.size != length + 4) { // 帧头 + 命令 + 长度 + 数据 + CRC if (data.size != length + 4) { // 帧头 + 命令 + 长度 + 数据 + CRC
FileLogger.e(TAG, "设备响应数据长度不匹配: 期望=${length+4}, 实际=${data.size}") FileLogger.e(TAG, "设备响应数据长度不匹配: 期望=${length + 4}, 实际=${data.size}")
return return
} }
@ -540,7 +571,10 @@ object BleService {
val receivedCrc = data[data.size - 1].toInt() and 0xFF val receivedCrc = data[data.size - 1].toInt() and 0xFF
if (calculatedCrc.toInt() and 0xFF != receivedCrc) { if (calculatedCrc.toInt() and 0xFF != receivedCrc) {
FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") FileLogger.e(
TAG,
"设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}"
)
return return
} }
@ -550,7 +584,16 @@ object BleService {
System.arraycopy(data, 3, responseData, 0, length) System.arraycopy(data, 3, responseData, 0, length)
} }
FileLogger.d(TAG, "收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${responseData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${
responseData.joinToString(", ") {
"0x${
(it.toInt() and 0xFF).toString(16)
}"
}
}"
)
// 根据命令类型处理不同的响应 // 根据命令类型处理不同的响应
when (cmdType) { when (cmdType) {
@ -563,13 +606,15 @@ object BleService {
if (responseData.isNotEmpty()) { if (responseData.isNotEmpty()) {
// 转换为字符串前,确认是否是有效的ASCII字符 // 转换为字符串前,确认是否是有效的ASCII字符
val version = responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } val version =
responseData.joinToString(".") { (it.toInt() and 0xFF).toString() }
resultMap["version"] = version resultMap["version"] = version
FileLogger.i(TAG, "接收到目标版本号: $version") FileLogger.i(TAG, "接收到目标版本号: $version")
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_PRODUCT_ID -> { BleConst.CMD_GET_PRODUCT_ID -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -578,13 +623,16 @@ object BleService {
) )
if (responseData.isNotEmpty() && responseData.size >= 2) { if (responseData.isNotEmpty() && responseData.size >= 2) {
val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${(responseData[1].toInt() and 0xFF).toString(16)}" val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${
(responseData[1].toInt() and 0xFF).toString(16)
}"
resultMap["productId"] = productId resultMap["productId"] = productId
FileLogger.i(TAG, "接收到产品ID: $productId") FileLogger.i(TAG, "接收到产品ID: $productId")
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_COLOR_ID -> { BleConst.CMD_GET_COLOR_ID -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -600,6 +648,7 @@ object BleService {
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_BATTERY_INFO -> { BleConst.CMD_GET_BATTERY_INFO -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -612,11 +661,15 @@ object BleService {
val leftEarBattery = responseData[0].toInt() and 0x7F val leftEarBattery = responseData[0].toInt() and 0x7F
val leftEarCharging = (responseData[0].toInt() and 0x80) != 0 val leftEarCharging = (responseData[0].toInt() and 0x80) != 0
val rightEarBattery = if (responseData.size > 1) responseData[1].toInt() and 0x7F else 0 val rightEarBattery =
val rightEarCharging = if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false if (responseData.size > 1) responseData[1].toInt() and 0x7F else 0
val rightEarCharging =
if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false
val caseBattery = if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0 val caseBattery =
val caseCharging = if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0
val caseCharging =
if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false
// 添加到结果Map // 添加到结果Map
resultMap["leftEarBattery"] = leftEarBattery resultMap["leftEarBattery"] = leftEarBattery
@ -627,14 +680,17 @@ object BleService {
resultMap["caseCharging"] = caseCharging resultMap["caseCharging"] = caseCharging
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示
FileLogger.i(TAG, "接收到电量信息: 左耳=${leftEarBattery}% " + FileLogger.i(
"(${if(leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + TAG, "接收到电量信息: 左耳=${leftEarBattery}% " +
"(${if(rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + "(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " +
"(${if(caseCharging) "充电中" else "未充电"})") "(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " +
"(${if (caseCharging) "充电中" else "未充电"})"
)
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_CONTROL_CODEC -> { BleConst.CMD_CONTROL_CODEC -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -673,6 +729,7 @@ object BleService {
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
else -> { else -> {
// 对于未知命令,返回原始数据 // 对于未知命令,返回原始数据
val resultMap = mapOf( val resultMap = mapOf(
@ -691,7 +748,10 @@ object BleService {
* 处理设备主动上报数据 - 帧头为0xCC * 处理设备主动上报数据 - 帧头为0xCC
*/ */
private fun processDeviceNotification(data: ByteArray) { private fun processDeviceNotification(data: ByteArray) {
FileLogger.d(TAG, "收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}"
)
if (data.size < 3) { if (data.size < 3) {
FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短") FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短")
return return
@ -703,7 +763,7 @@ object BleService {
// 检查数据长度 // 检查数据长度
if (data.size != length + 4) { // 帧头 + 类型 + 长度 + 数据 + CRC if (data.size != length + 4) { // 帧头 + 类型 + 长度 + 数据 + CRC
FileLogger.e(TAG, "设备主动上报数据长度不匹配: 期望=${length+4}, 实际=${data.size}") FileLogger.e(TAG, "设备主动上报数据长度不匹配: 期望=${length + 4}, 实际=${data.size}")
return return
} }
@ -713,7 +773,10 @@ object BleService {
val receivedCrc = data[data.size - 1].toInt() and 0xFF val receivedCrc = data[data.size - 1].toInt() and 0xFF
if (calculatedCrc.toInt() and 0xFF != receivedCrc) { if (calculatedCrc.toInt() and 0xFF != receivedCrc) {
FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") FileLogger.e(
TAG,
"设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}"
)
return return
} }
@ -723,7 +786,16 @@ object BleService {
System.arraycopy(data, 3, notifyData, 0, length) System.arraycopy(data, 3, notifyData, 0, length)
} }
FileLogger.d(TAG, "收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${notifyData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${
notifyData.joinToString(", ") {
"0x${
(it.toInt() and 0xFF).toString(16)
}"
}
}"
)
// 根据上报类型处理 // 根据上报类型处理
when (infoType) { when (infoType) {
@ -740,11 +812,14 @@ object BleService {
val leftEarBattery = notifyData[0].toInt() and 0x7F val leftEarBattery = notifyData[0].toInt() and 0x7F
val leftEarCharging = (notifyData[0].toInt() and 0x80) != 0 val leftEarCharging = (notifyData[0].toInt() and 0x80) != 0
val rightEarBattery = if (notifyData.size > 1) notifyData[1].toInt() and 0x7F else 0 val rightEarBattery =
val rightEarCharging = if (notifyData.size > 1) (notifyData[1].toInt() and 0x80) != 0 else false if (notifyData.size > 1) notifyData[1].toInt() and 0x7F else 0
val rightEarCharging =
if (notifyData.size > 1) (notifyData[1].toInt() and 0x80) != 0 else false
val caseBattery = if (notifyData.size > 2) notifyData[2].toInt() and 0x7F else 0 val caseBattery = if (notifyData.size > 2) notifyData[2].toInt() and 0x7F else 0
val caseCharging = if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false val caseCharging =
if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false
// 添加到结果Map // 添加到结果Map
@ -756,14 +831,17 @@ object BleService {
resultMap["caseCharging"] = caseCharging resultMap["caseCharging"] = caseCharging
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示
FileLogger.i(TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " + FileLogger.i(
"(${if(leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " +
"(${if(rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + "(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " +
"(${if(caseCharging) "充电中" else "未充电"})") "(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " +
"(${if (caseCharging) "充电中" else "未充电"})"
)
} }
notifyDeviceInfoReceived(infoType, resultMap) notifyDeviceInfoReceived(infoType, resultMap)
} }
BleConst.CMD_WAKE_UP -> { BleConst.CMD_WAKE_UP -> {
FileLogger.i(TAG, "收到唤醒指令") FileLogger.i(TAG, "收到唤醒指令")
// 接收到唤醒信号,打开编码器 // 接收到唤醒信号,打开编码器
@ -827,7 +905,10 @@ object BleService {
*/ */
fun closeCodec(): Boolean { fun closeCodec(): Boolean {
FileLogger.i(TAG, "关闭编解码...") FileLogger.i(TAG, "关闭编解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte())) return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte())
)
} }
/** /**
@ -835,7 +916,10 @@ object BleService {
*/ */
fun openDecoder(): Boolean { fun openDecoder(): Boolean {
FileLogger.i(TAG, "打开解码...") FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte())) return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte())
)
} }
/** /**
@ -851,7 +935,13 @@ object BleService {
*/ */
fun openEncoder(): Boolean { fun openEncoder(): Boolean {
FileLogger.i(TAG, "打开编码...") FileLogger.i(TAG, "打开编码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_ENCODE_ON.toByte())) return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
} }
/** /**
@ -870,10 +960,12 @@ object BleService {
} }
// 高级控制:设置命令类型、长度和两个参数 // 高级控制:设置命令类型、长度和两个参数
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( return sendCommand(
0xA1.toByte(), // 状态码 - 使用解码状态 BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
channelMode.toByte() // 声道模式 0xA1.toByte(), // 状态码 - 使用解码状态
)) channelMode.toByte() // 声道模式
)
)
} }
/** /**
@ -1046,9 +1138,12 @@ object BleService {
cmdPacket[3 + cmdLength] = crc cmdPacket[3 + cmdLength] = crc
// 记录命令详情 // 记录命令详情
FileLogger.d(TAG, "发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " + FileLogger.d(
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " + TAG,
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}") "发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " +
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " +
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}"
)
// 发送数据 // 发送数据
writeChar!!.value = cmdPacket writeChar!!.value = cmdPacket

5
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/ChatAgent.kt

@ -31,7 +31,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
FileLogger.d(TAG, "初始化语音代理..., $config") FileLogger.d(TAG, "初始化语音代理..., $config")
// 1. 初始化AgentService // 1. 初始化AgentService
if (!AgentService.initialize(context, config)) { if (!AgentService.initialize(context, config, true)) {
FileLogger.e(TAG, "语音服务初始化失败") FileLogger.e(TAG, "语音服务初始化失败")
return false return false
} }
@ -67,7 +67,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
return false return false
} }
return AgentService.startRecognition() return AgentService.startRecognition(true)
} }
/** /**
@ -127,6 +127,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
} }
override fun onAudioDataReceived(data: ByteArray) { override fun onAudioDataReceived(data: ByteArray) {
AgentService.azureAsrHelper?.pushAudioData(data)
// 可选:处理音频数据 // 可选:处理音频数据
} }

16
local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt

@ -70,6 +70,7 @@ class BleService(private val context: Context) {
// 获取插件实例 // 获取插件实例
private val opusPlugin = JlOpusPlugin() private val opusPlugin = JlOpusPlugin()
// 蓝牙管理器和适配器 // 蓝牙管理器和适配器
private val bluetoothManager = private val bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@ -325,13 +326,14 @@ class BleService(private val context: Context) {
// 注册特征通知 // 注册特征通知
setupNotifications(gatt) setupNotifications(gatt)
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object : opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object :
JlOpusPlugin.Callback1 { JlOpusPlugin.CallbackOpus {
override fun onAudioDataReceived1(pcmData: ByteArray) { override fun onAudioDataReceivedOpus(pcmData: ByteArray) {
// Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}") // Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}")
callback?.onAudioDataReceived(pcmData) callback?.onAudioDataReceived(pcmData)
// 处理解码后的 PCM 数据(如播放或写入文件) // 处理解码后的 PCM 数据(如播放或写入文件)
}}) }
})
} else { } else {
Log.e(TAG, "未找到所需特征") Log.e(TAG, "未找到所需特征")
updateConnectionState(STATE_ERROR) updateConnectionState(STATE_ERROR)
@ -356,7 +358,7 @@ class BleService(private val context: Context) {
characteristic: BluetoothGattCharacteristic characteristic: BluetoothGattCharacteristic
) { ) {
val characteristicUuid = characteristic.uuid.toString() val characteristicUuid = characteristic.uuid.toString()
// Log.d(TAG, "收到特征变化通知: $characteristicUuid") // Log.d(TAG, "收到特征变化通知: $characteristicUuid")
when (characteristicUuid) { when (characteristicUuid) {
// NOTIFY_CHAR_UUID -> { // NOTIFY_CHAR_UUID -> {
@ -369,7 +371,7 @@ class BleService(private val context: Context) {
NOTIFY_CHAR_UUID -> { NOTIFY_CHAR_UUID -> {
// 处理控制消息 // 处理控制消息
val value = characteristic.value val value = characteristic.value
// Log.i(TAG, "收到控制通知: ${value.size}") // Log.i(TAG, "收到控制通知: ${value.size}")
// 判断是否收到音频数据 // 判断是否收到音频数据
if (value.isNotEmpty() && (value[0].toInt() and 0xFF == 0x4b || value[0].toInt() and 0xFF == 0x48)) { if (value.isNotEmpty() && (value[0].toInt() and 0xFF == 0x4b || value[0].toInt() and 0xFF == 0x48)) {

653
local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

@ -19,359 +19,408 @@ import io.flutter.plugin.common.MethodChannel.Result
import java.io.File import java.io.File
/** JlOpusPlugin */ /** JlOpusPlugin */
class JlOpusPlugin: FlutterPlugin, MethodCallHandler { class JlOpusPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private lateinit var eventChannel: EventChannel private lateinit var eventChannel: EventChannel
private lateinit var context: Context private lateinit var context: Context
private var opusManager: OpusManager? = null private var opusManager: OpusManager? = null
private var streamSink: EventChannel.EventSink? = null private var streamSink: EventChannel.EventSink? = null
// 修正1:正确的主构造函数写法
constructor() { // 修正1:正确的主构造函数写法
constructor() {
opusManager = OpusManager() opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功") Log.d(TAG, "OpusManager初始化成功")
} }
// 添加主线程Handler用于确保在主线程回调 // 添加主线程Handler用于确保在主线程回调
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
interface Callback1 {
fun onAudioDataReceived1(data: ByteArray) interface CallbackOpus {
fun onAudioDataReceivedOpus(data: ByteArray)
} }
companion object { companion object {
private const val TAG = "JlOpusPlugin" private const val TAG = "JlOpusPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus" private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events" private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events"
} }
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME) channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME) eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler { eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
streamSink = events streamSink = events
} }
override fun onCancel(arguments: Any?) { override fun onCancel(arguments: Any?) {
streamSink = null streamSink = null
} }
}) })
// 初始化OpusManager // 初始化OpusManager
try { try {
opusManager = OpusManager() opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功") Log.d(TAG, "OpusManager初始化成功")
} catch (e: OpusException) { } catch (e: OpusException) {
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e) Log.e(TAG, "OpusManager初始化失败: ${e.message}", e)
} }
}
// 在主线程上安全发送事件
private fun sendEventOnMainThread(event: Map<String, Any?>) {
if (streamSink == null) return
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接发送
streamSink?.success(event)
} else {
// 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
}
}
// 在主线程上安全返回结果
private fun sendResultOnMainThread(result: Result, value: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.success(value)
} else {
// 否则切换到主线程
mainHandler.post {
result.success(value)
}
}
}
// 在主线程上安全返回错误
private fun sendErrorOnMainThread(result: Result, errorCode: String, errorMessage: String, errorDetails: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else {
// 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
} }
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { // 在主线程上安全发送事件
if (opusManager == null) { private fun sendEventOnMainThread(event: Map<String, Any?>) {
result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null) if (streamSink == null) return
return
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接发送
streamSink?.success(event)
} else {
// 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
}
} }
when (call.method) { // 在主线程上安全返回结果
"initOpusDecoder" -> { private fun sendResultOnMainThread(result: Result, value: Any?) {
result.success(opusManager != null) if (Looper.myLooper() == Looper.getMainLooper()) {
} // 如果已经在主线程,直接返回
"decodeOpusFile" -> { result.success(value)
val inPath = call.argument<String>("inPath") } else {
val outPath = call.argument<String>("outPath") // 否则切换到主线程
val hasHeader = call.argument<Boolean>("hasHeader") ?: false mainHandler.post {
val channel = call.argument<Int>("channel") ?: 1 result.success(value)
val sampleRate = call.argument<Int>("sampleRate") ?: 16000 }
val packetSize = call.argument<Int>("packetSize") ?: 40
if (inPath == null || outPath == null) {
result.error("INVALID_ARGS", "输入或输出路径不能为空", null)
return
} }
}
// 确保输入文件存在 // 在主线程上安全返回错误
val inFile = File(inPath) private fun sendErrorOnMainThread(
if (!inFile.exists()) { result: Result,
result.error("FILE_NOT_FOUND", "输入文件不存在: $inPath", null) errorCode: String,
return errorMessage: String,
errorDetails: Any?
) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else {
// 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
} }
}
// 创建解码参数 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
val option = OpusOption() if (opusManager == null) {
.setHasHead(hasHeader) result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null)
.setChannel(channel) return
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码文件, 参数: $option, 输入: $inPath, 输出: $outPath")
opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback {
override fun onStart() {
Log.d(TAG, "文件解码开始")
sendEventOnMainThread(mapOf(
"event" to "onStart",
"type" to "file"
))
}
override fun onComplete(outFilePath: String?) {
Log.d(TAG, "文件解码完成: $outFilePath")
sendEventOnMainThread(mapOf(
"event" to "onComplete",
"type" to "file",
"filePath" to outFilePath
))
sendResultOnMainThread(result, outFilePath)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "文件解码错误: [$code] $message")
sendEventOnMainThread(mapOf(
"event" to "onError",
"type" to "file",
"code" to code,
"message" to message
))
sendErrorOnMainThread(result, "DECODE_ERROR", "解码出错: [$code] $message", null)
}
})
}
"startDecodeStream" -> {
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
// 如果已经在解码流,先停止
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
} }
// 创建解码参数 when (call.method) {
val option = OpusOption() "initOpusDecoder" -> {
.setHasHead(hasHeader) result.success(opusManager != null)
.setChannel(channel) }
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码数据流, 参数: $option") "decodeOpusFile" -> {
val inPath = call.argument<String>("inPath")
val outPath = call.argument<String>("outPath")
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
if (inPath == null || outPath == null) {
result.error("INVALID_ARGS", "输入或输出路径不能为空", null)
return
}
// 确保输入文件存在
val inFile = File(inPath)
if (!inFile.exists()) {
result.error("FILE_NOT_FOUND", "输入文件不存在: $inPath", null)
return
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码文件, 参数: $option, 输入: $inPath, 输出: $outPath")
opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback {
override fun onStart() {
Log.d(TAG, "文件解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "file"
)
)
}
override fun onComplete(outFilePath: String?) {
Log.d(TAG, "文件解码完成: $outFilePath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "file",
"filePath" to outFilePath
)
)
sendResultOnMainThread(result, outFilePath)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "文件解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "file",
"code" to code,
"message" to message
)
)
sendErrorOnMainThread(
result,
"DECODE_ERROR",
"解码出错: [$code] $message",
null
)
}
})
}
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { "startDecodeStream" -> {
override fun onDecodeStream(data: ByteArray?) { val hasHeader = call.argument<Boolean>("hasHeader") ?: false
if (data != null) { val channel = call.argument<Int>("channel") ?: 1
sendEventOnMainThread(mapOf( val sampleRate = call.argument<Int>("sampleRate") ?: 16000
"event" to "onDecodeStream", val packetSize = call.argument<Int>("packetSize") ?: 40
"data" to data
)) // 如果已经在解码流,先停止
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码数据流, 参数: $option")
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
sendEventOnMainThread(
mapOf(
"event" to "onDecodeStream",
"data" to data
)
)
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "stream"
)
)
sendResultOnMainThread(result, true)
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "stream"
)
)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
)
)
sendErrorOnMainThread(
result,
"DECODE_STREAM_ERROR",
"数据流解码出错: [$code] $message",
null
)
}
})
} }
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(mapOf(
"event" to "onStart",
"type" to "stream"
))
sendResultOnMainThread(result, true)
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(mapOf(
"event" to "onComplete",
"type" to "stream"
))
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
))
sendErrorOnMainThread(result, "DECODE_STREAM_ERROR", "数据流解码出错: [$code] $message", null)
}
})
}
"stopDecodeStream" -> {
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
result.success(true)
} else {
result.success(false)
}
}
"writeAudioStream" -> {
val data = call.argument<ByteArray>("data")
if (data == null) {
result.error("INVALID_ARGS", "音频数据不能为空", null)
return
}
if (opusManager?.isDecodeStream == true) { "stopDecodeStream" -> {
opusManager?.writeAudioStream(data) if (opusManager?.isDecodeStream == true) {
result.success(true) opusManager?.stopDecodeStream()
} else { result.success(true)
result.error("NOT_DECODING", "当前没有处于解码状态", null) } else {
} result.success(false)
} }
"isDecoding" -> { }
result.success(opusManager?.isDecodeStream == true)
} "writeAudioStream" -> {
"dispose" -> { val data = call.argument<ByteArray>("data")
opusManager?.let { if (data == null) {
if (it.isDecodeStream) { result.error("INVALID_ARGS", "音频数据不能为空", null)
it.stopDecodeStream() return
} }
it.release()
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(data)
result.success(true)
} else {
result.error("NOT_DECODING", "当前没有处于解码状态", null)
}
}
"isDecoding" -> {
result.success(opusManager?.isDecodeStream == true)
}
"dispose" -> {
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
result.success(true)
}
else -> {
result.notImplemented()
}
} }
opusManager = null
result.success(true)
}
else -> {
result.notImplemented()
}
} }
}
fun handleDecodeRequest(opusdata: ByteArray) { fun handleDecodeRequest(opusdata: ByteArray) {
// Log.d(TAG, "handleDecodeRequest${opusdata}") // Log.d(TAG, "handleDecodeRequest${opusdata}")
if (opusdata == null) { if (opusdata == null) {
return return
} }
if (opusManager?.isDecodeStream == true) { if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(opusdata) opusManager?.writeAudioStream(opusdata)
} else { } else {
} }
} }
fun startOpusDecodeStream(
hasHeader: Boolean, fun startOpusDecodeStream(
channel: Int, hasHeader: Boolean,
sampleRate: Int, channel: Int,
packetSize: Int, sampleRate: Int,
callback: Callback1 packetSize: Int,
callback: CallbackOpus
) {
Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager") ) {
// 如果已经在解码流,先停止 Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager")
// 如果已经在解码流,先停止
if (opusManager?.isDecodeStream == true) { if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream() opusManager?.stopDecodeStream()
} }
// 创建解码参数 // 创建解码参数
val option = OpusOption() val option = OpusOption()
.setHasHead(hasHeader) .setHasHead(hasHeader)
.setChannel(channel) .setChannel(channel)
.setSampleRate(sampleRate) .setSampleRate(sampleRate)
.setPacketSize(packetSize) .setPacketSize(packetSize)
Log.d(TAG, "开始解码数据流, 参数: $option") Log.d(TAG, "开始解码数据流, 参数: $option")
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) { override fun onDecodeStream(data: ByteArray?) {
if (data != null) { if (data != null) {
//Log.d(TAG, "onDecodeStream${data}") //Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceived1(data) callback.onAudioDataReceivedOpus(data)
sendEventOnMainThread(mapOf( sendEventOnMainThread(
"event" to "onDecodeStream", mapOf(
"data" to data "event" to "onDecodeStream",
)) "data" to data
)
)
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "stream"
)
)
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "stream"
)
)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
)
)
} }
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(mapOf(
"event" to "onStart",
"type" to "stream"
))
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(mapOf(
"event" to "onComplete",
"type" to "stream"
))
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
))
}
}) })
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
} }
opusManager = null
} override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
}
} }
Loading…
Cancel
Save