Browse Source

修改ble通信

newdev_shunjiawei
fdp 1 year ago
parent
commit
a15cf42bbb
  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. 395
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  8. 49
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/ChatAgent.kt
  9. 18
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt
  10. 695
      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"

395
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服务类:提供蓝牙低功耗设备的扫描、连接和通信功能
@ -44,14 +45,14 @@ object BleService {
interface Callback { interface Callback {
// 扫描相关回调 // 扫描相关回调
fun onScanResult(devices: List<Map<String, Any>>) fun onScanResult(devices: List<Map<String, Any>>)
// 连接状态相关回调 // 连接状态相关回调
fun onConnectionStateChanged(state: Int) fun onConnectionStateChanged(state: Int)
// 数据相关回调 // 数据相关回调
fun onAudioDataReceived(data: ByteArray) fun onAudioDataReceived(data: ByteArray)
fun onWakeupSignalReceived() fun onWakeupSignalReceived()
// 设备信息相关回调 - 统一回调接口 // 设备信息相关回调 - 统一回调接口
fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>)
} }
@ -59,7 +60,7 @@ object BleService {
// ====================================================================================================== // ======================================================================================================
// 成员变量 // 成员变量
// ====================================================================================================== // ======================================================================================================
// 上下文 // 上下文
private lateinit var context: Context private lateinit var context: Context
@ -84,8 +85,10 @@ object BleService {
// 回调集合 - 使用线程安全的集合存储所有回调 // 回调集合 - 使用线程安全的集合存储所有回调
private val callbacks = CopyOnWriteArrayList<Callback>() private val callbacks = CopyOnWriteArrayList<Callback>()
// 获取插件实例
private val opusPlugin = JlOpusPlugin()
// 初始化状态 // 初始化状态
private var isInitialized = false private var isInitialized = false
@ -96,18 +99,19 @@ object BleService {
*/ */
fun initialize(appContext: Context): Boolean { fun initialize(appContext: Context): Boolean {
if (isInitialized) return true if (isInitialized) return true
try { try {
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
// 初始化Handler // 初始化Handler
scanHandler = Handler(Looper.getMainLooper()) scanHandler = Handler(Looper.getMainLooper())
isInitialized = true isInitialized = true
FileLogger.d(TAG, "BLE服务初始化成功") FileLogger.d(TAG, "BLE服务初始化成功")
return true return true
@ -116,7 +120,7 @@ object BleService {
return false return false
} }
} }
/** /**
* 添加回调监听器 * 添加回调监听器
* @param callback 要添加的回调监听器 * @param callback 要添加的回调监听器
@ -130,7 +134,7 @@ object BleService {
} }
return false return false
} }
/** /**
* 移除回调监听器 * 移除回调监听器
* @param callback 要移除的回调监听器 * @param callback 要移除的回调监听器
@ -143,7 +147,7 @@ object BleService {
} }
return removed return removed
} }
/** /**
* 移除所有回调监听器 * 移除所有回调监听器
*/ */
@ -165,7 +169,7 @@ object BleService {
FileLogger.e(TAG, "BLE服务未初始化") FileLogger.e(TAG, "BLE服务未初始化")
return false return false
} }
// 获取一个局部引用,避免并发访问问题 // 获取一个局部引用,避免并发访问问题
val scanner = bluetoothLeScanner ?: return false val scanner = bluetoothLeScanner ?: return false
if (isScanning) return false if (isScanning) return false
@ -211,17 +215,17 @@ object BleService {
// 获取一个局部引用,避免并发访问问题 // 获取一个局部引用,避免并发访问问题
val scanner = bluetoothLeScanner ?: return false val scanner = bluetoothLeScanner ?: return false
if (!isScanning) return false if (!isScanning) return false
FileLogger.i(TAG, "停止主动扫描") FileLogger.i(TAG, "停止主动扫描")
try { try {
scanner.stopScan(scanCallback) scanner.stopScan(scanCallback)
scanHandler.removeCallbacksAndMessages(null) scanHandler.removeCallbacksAndMessages(null)
isScanning = false isScanning = false
// 重新注册后台扫描,保证唤醒能力 // 重新注册后台扫描,保证唤醒能力
registerBackgroundScan() registerBackgroundScan()
return true return true
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "停止扫描失败: ${e.message}", e) FileLogger.e(TAG, "停止扫描失败: ${e.message}", e)
@ -235,24 +239,25 @@ object BleService {
private val scanCallback = object : ScanCallback() { private val scanCallback = object : ScanCallback() {
override fun onScanResult(type: Int, result: ScanResult) { override fun onScanResult(type: Int, result: ScanResult) {
val dev = result.device ?: return val dev = result.device ?: return
// 打印设备信息和服务UUID // 打印设备信息和服务UUID
printDeviceInfo(result) printDeviceInfo(result)
// 直接上报每个扫描结果 // 直接上报每个扫描结果
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 ?: "未知"),
"rssi" to result.rssi, "rssi" to result.rssi,
"uuids" to uuids "uuids" to uuids
) )
// 直接回调单个设备结果 // 直接回调单个设备结果
notifyScanResult(listOf(deviceMap)) notifyScanResult(listOf(deviceMap))
// 更新缓存 // 更新缓存
val idx = scanResults.indexOfFirst { it.device.address == dev.address } val idx = scanResults.indexOfFirst { it.device.address == dev.address }
if (idx >= 0) scanResults[idx] = result else scanResults.add(result) if (idx >= 0) scanResults[idx] = result else scanResults.add(result)
@ -273,17 +278,20 @@ 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}, " +
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]") FileLogger.d(
TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " +
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]"
)
// 打印广播数据的详细信息 // 打印广播数据的详细信息
val scanData = scanRecord?.bytes val scanData = scanRecord?.bytes
if (scanData != null) { if (scanData != null) {
val hexString = scanData.joinToString("") { String.format("%02X", it) } val hexString = scanData.joinToString("") { String.format("%02X", it) }
FileLogger.d(TAG, "广播数据: $hexString") FileLogger.d(TAG, "广播数据: $hexString")
// 解析服务数据 // 解析服务数据
scanRecord.serviceData?.forEach { (uuid, data) -> scanRecord.serviceData?.forEach { (uuid, data) ->
val dataHex = data.joinToString("") { String.format("%02X", it) } val dataHex = data.joinToString("") { String.format("%02X", it) }
@ -304,13 +312,13 @@ object BleService {
FileLogger.w(TAG, "已经连接中,忽略连接请求") FileLogger.w(TAG, "已经连接中,忽略连接请求")
return return
} }
// 获取一个局部引用,避免并发访问问题 // 获取一个局部引用,避免并发访问问题
val adapter = bluetoothAdapter ?: run { val adapter = bluetoothAdapter ?: run {
updateConnectionState(BleConst.STATE_ERROR) updateConnectionState(BleConst.STATE_ERROR)
return return
} }
// 关闭现有连接 // 关闭现有连接
disconnectGatt() disconnectGatt()
@ -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,13 +401,14 @@ 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)
// 获取音频服务特征 // 获取音频服务特征
val audioSvc = g.getService(BleConst.AUDIO_SERVICE_UUID) val audioSvc = g.getService(BleConst.AUDIO_SERVICE_UUID)
audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID) audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID)
if (notifyChar == null || writeChar == null) { if (notifyChar == null || writeChar == null) {
FileLogger.e(TAG, "未找到主服务所需特征") FileLogger.e(TAG, "未找到主服务所需特征")
updateConnectionState(BleConst.STATE_ERROR) updateConnectionState(BleConst.STATE_ERROR)
@ -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,13 +424,22 @@ 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区分处理
when (c.uuid) { when (c.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(
@ -476,7 +506,7 @@ object BleService {
FileLogger.w(TAG, "特征不支持通知功能: ${characteristic?.uuid}") FileLogger.w(TAG, "特征不支持通知功能: ${characteristic?.uuid}")
return return
} }
g.setCharacteristicNotification(characteristic, true) g.setCharacteristicNotification(characteristic, true)
val desc = characteristic.getDescriptor(BleConst.CLIENT_CHAR_CONFIG_UUID) val desc = characteristic.getDescriptor(BleConst.CLIENT_CHAR_CONFIG_UUID)
desc?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE desc?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
@ -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,15 +531,18 @@ 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
} }
val header = data[0] // 0xBB val header = data[0] // 0xBB
val cmdType = data[1].toInt() and 0xFF val cmdType = data[1].toInt() and 0xFF
// 判断命令失败响应(长度为0) // 判断命令失败响应(长度为0)
if (data.size == 4 && data[2].toInt() and 0xFF == 0) { if (data.size == 4 && data[2].toInt() and 0xFF == 0) {
FileLogger.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败") FileLogger.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败")
@ -524,34 +555,46 @@ object BleService {
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
return return
} }
// 正常响应处理 // 正常响应处理
val length = data[2].toInt() and 0xFF val length = data[2].toInt() and 0xFF
// 检查数据长度 // 检查数据长度
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
} }
// 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节 // 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节
val payload = data.sliceArray(0 until data.size - 1) val payload = data.sliceArray(0 until data.size - 1)
val calculatedCrc = crc8Maxim(payload, payload.size) val calculatedCrc = crc8Maxim(payload, payload.size)
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
} }
// 提取响应数据 // 提取响应数据
val responseData = if (length > 0) ByteArray(length) else ByteArray(0) val responseData = if (length > 0) ByteArray(length) else ByteArray(0)
if (length > 0) { if (length > 0) {
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) {
BleConst.CMD_GET_VERSION -> { BleConst.CMD_GET_VERSION -> {
@ -560,64 +603,74 @@ object BleService {
"command" to "versionInfo", "command" to "versionInfo",
"rawData" to responseData "rawData" to responseData
) )
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,
"command" to "productInfo", "command" to "productInfo",
"rawData" to responseData "rawData" to responseData
) )
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,
"command" to "colorInfo", "command" to "colorInfo",
"rawData" to responseData "rawData" to responseData
) )
if (responseData.isNotEmpty()) { if (responseData.isNotEmpty()) {
val colorId = (responseData[0].toInt() and 0xFF).toString() val colorId = (responseData[0].toInt() and 0xFF).toString()
resultMap["colorId"] = colorId resultMap["colorId"] = colorId
FileLogger.i(TAG, "接收到耳机颜色ID: $colorId") FileLogger.i(TAG, "接收到耳机颜色ID: $colorId")
} }
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,
"command" to "batteryInfo", "command" to "batteryInfo",
"rawData" to responseData "rawData" to responseData
) )
if (responseData.isNotEmpty() && responseData.size >= 3) { if (responseData.isNotEmpty() && responseData.size >= 3) {
// 处理电池信息 - 左耳、右耳、充电仓 // 处理电池信息 - 左耳、右耳、充电仓
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 =
val caseBattery = if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0 if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false
val caseCharging = if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false
val caseBattery =
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
resultMap["leftEarCharging"] = leftEarCharging resultMap["leftEarCharging"] = leftEarCharging
@ -626,26 +679,29 @@ object BleService {
resultMap["caseBattery"] = caseBattery resultMap["caseBattery"] = caseBattery
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,
"command" to cmdType, "command" to cmdType,
"rawData" to responseData "rawData" to responseData
) )
if (responseData.isNotEmpty() && responseData.size >= 2) { if (responseData.isNotEmpty() && responseData.size >= 2) {
val codecStatus = responseData[0].toInt() and 0xFF val codecStatus = responseData[0].toInt() and 0xFF
val channelMode = responseData[1].toInt() and 0xFF val channelMode = responseData[1].toInt() and 0xFF
// 解码状态描述 // 解码状态描述
val statusDesc = when (codecStatus) { val statusDesc = when (codecStatus) {
BleConst.CODEC_CONTROL_CLOSE -> "已关闭编解码" BleConst.CODEC_CONTROL_CLOSE -> "已关闭编解码"
@ -654,7 +710,7 @@ object BleService {
BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码" BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码"
else -> "未知状态($codecStatus)" else -> "未知状态($codecStatus)"
} }
// 声道模式描述 // 声道模式描述
val channelDesc = when (channelMode) { val channelDesc = when (channelMode) {
BleConst.AUDIO_CHANNEL_LEFT -> "左声道" BleConst.AUDIO_CHANNEL_LEFT -> "左声道"
@ -662,17 +718,18 @@ object BleService {
BleConst.AUDIO_CHANNEL_STEREO -> "立体声" BleConst.AUDIO_CHANNEL_STEREO -> "立体声"
else -> "未知声道($channelMode)" else -> "未知声道($channelMode)"
} }
resultMap["codecStatus"] = codecStatus resultMap["codecStatus"] = codecStatus
resultMap["channelMode"] = channelMode resultMap["channelMode"] = channelMode
resultMap["statusDesc"] = statusDesc resultMap["statusDesc"] = statusDesc
resultMap["channelDesc"] = channelDesc resultMap["channelDesc"] = channelDesc
FileLogger.i(TAG, "编解码控制响应: $statusDesc, $channelDesc") FileLogger.i(TAG, "编解码控制响应: $statusDesc, $channelDesc")
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
else -> { else -> {
// 对于未知命令,返回原始数据 // 对于未知命令,返回原始数据
val resultMap = mapOf( val resultMap = mapOf(
@ -686,45 +743,60 @@ 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
} }
val header = data[0] // 0xCC val header = data[0] // 0xCC
val infoType = data[1].toInt() and 0xFF val infoType = data[1].toInt() and 0xFF
val length = data[2].toInt() and 0xFF val length = data[2].toInt() and 0xFF
// 检查数据长度 // 检查数据长度
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
} }
// 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节 // 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节
val payload = data.sliceArray(0 until data.size - 1) val payload = data.sliceArray(0 until data.size - 1)
val calculatedCrc = crc8Maxim(payload, payload.size) val calculatedCrc = crc8Maxim(payload, payload.size)
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
} }
// 提取上报数据 // 提取上报数据
val notifyData = if (length > 0) ByteArray(length) else ByteArray(0) val notifyData = if (length > 0) ByteArray(length) else ByteArray(0)
if (length > 0) { if (length > 0) {
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) {
BleConst.CMD_GET_BATTERY_INFO -> { // 电量信息 BleConst.CMD_GET_BATTERY_INFO -> { // 电量信息
@ -734,19 +806,22 @@ object BleService {
"rawData" to notifyData, "rawData" to notifyData,
"isNotification" to true // 标记为主动上报 "isNotification" to true // 标记为主动上报
) )
if (notifyData.isNotEmpty() && notifyData.size >= 3) { if (notifyData.isNotEmpty() && notifyData.size >= 3) {
// 处理电池信息 - 左耳、右耳、充电仓 // 处理电池信息 - 左耳、右耳、充电仓
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
resultMap["leftEarBattery"] = leftEarBattery resultMap["leftEarBattery"] = leftEarBattery
resultMap["leftEarCharging"] = leftEarCharging resultMap["leftEarCharging"] = leftEarCharging
@ -755,15 +830,18 @@ object BleService {
resultMap["caseBattery"] = caseBattery resultMap["caseBattery"] = caseBattery
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, "收到唤醒指令")
// 接收到唤醒信号,打开编码器 // 接收到唤醒信号,打开编码器
@ -797,7 +875,7 @@ object BleService {
FileLogger.i(TAG, "获取耳机版本号...") FileLogger.i(TAG, "获取耳机版本号...")
return sendCommand(BleConst.CMD_GET_VERSION.toByte(), byteArrayOf(0xFF.toByte())) return sendCommand(BleConst.CMD_GET_VERSION.toByte(), byteArrayOf(0xFF.toByte()))
} }
/** /**
* 获取产品ID * 获取产品ID
*/ */
@ -805,7 +883,7 @@ object BleService {
FileLogger.i(TAG, "获取耳机产品ID...") FileLogger.i(TAG, "获取耳机产品ID...")
return sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte(), byteArrayOf(0xFF.toByte())) return sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte(), byteArrayOf(0xFF.toByte()))
} }
/** /**
* 获取耳机颜色ID * 获取耳机颜色ID
*/ */
@ -813,7 +891,7 @@ object BleService {
FileLogger.i(TAG, "获取耳机颜色ID...") FileLogger.i(TAG, "获取耳机颜色ID...")
return sendCommand(BleConst.CMD_GET_COLOR_ID.toByte(), byteArrayOf(0xFF.toByte())) return sendCommand(BleConst.CMD_GET_COLOR_ID.toByte(), byteArrayOf(0xFF.toByte()))
} }
/** /**
* 获取耳机电量信息 * 获取耳机电量信息
*/ */
@ -821,23 +899,29 @@ object BleService {
FileLogger.i(TAG, "获取耳机电量信息...") FileLogger.i(TAG, "获取耳机电量信息...")
return sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte(), byteArrayOf(0xFF.toByte())) return sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte(), byteArrayOf(0xFF.toByte()))
} }
/** /**
* 控制编解码 - 关闭编解码 * 控制编解码 - 关闭编解码
*/ */
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())
)
} }
/** /**
* 控制编解码 - 打开解码 * 控制编解码 - 打开解码
*/ */
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())
)
} }
/** /**
* 控制编解码 - A2DP播放 * 控制编解码 - A2DP播放
*/ */
@ -845,15 +929,21 @@ object BleService {
FileLogger.i(TAG, "切换至A2DP播放模式...") FileLogger.i(TAG, "切换至A2DP播放模式...")
return sendCommand(BleConst.CMD_PLAY_A2DP.toByte()) return sendCommand(BleConst.CMD_PLAY_A2DP.toByte())
} }
/** /**
* 控制编解码 - 打开编码 * 控制编解码 - 打开编码
*/ */
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()
)
)
} }
/** /**
* 设置声道模式 * 设置声道模式
* @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO * @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO
@ -868,14 +958,16 @@ object BleService {
return false return false
} }
} }
// 高级控制:设置命令类型、长度和两个参数 // 高级控制:设置命令类型、长度和两个参数
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( return sendCommand(
0xA1.toByte(), // 状态码 - 使用解码状态 BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
channelMode.toByte() // 声道模式 0xA1.toByte(), // 状态码 - 使用解码状态
)) channelMode.toByte() // 声道模式
)
)
} }
/** /**
* 音量增大 * 音量增大
*/ */
@ -883,7 +975,7 @@ object BleService {
FileLogger.i(TAG, "设备音量增加...") FileLogger.i(TAG, "设备音量增加...")
return sendCommand(BleConst.CMD_VOLUME_UP.toByte()) return sendCommand(BleConst.CMD_VOLUME_UP.toByte())
} }
/** /**
* 音量减小 * 音量减小
*/ */
@ -901,21 +993,21 @@ object BleService {
fun isConnected(): Boolean { fun isConnected(): Boolean {
return connectionState.value == BleConst.STATE_CONNECTED return connectionState.value == BleConst.STATE_CONNECTED
} }
/** /**
* 连接状态检查 * 连接状态检查
*/ */
private fun checkConn(): Boolean = private fun checkConn(): Boolean =
bluetoothGatt != null && writeChar != null && bluetoothGatt != null && writeChar != null &&
connectionState.value == BleConst.STATE_CONNECTED connectionState.value == BleConst.STATE_CONNECTED
/** /**
* 获取已连接设备地址 * 获取已连接设备地址
*/ */
fun getConnectedDeviceAddress(): String? { fun getConnectedDeviceAddress(): String? {
return bluetoothGatt?.device?.address return bluetoothGatt?.device?.address
} }
/** /**
* 获取蓝牙适配器 * 获取蓝牙适配器
*/ */
@ -976,7 +1068,7 @@ object BleService {
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT) val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java) .setClass(context, BleWakeReceiver::class.java)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
val pi = PendingIntent.getBroadcast( val pi = PendingIntent.getBroadcast(
context, 0, scanIntent, context, 0, scanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@ -1022,34 +1114,37 @@ object BleService {
try { try {
// 计算命令长度 // 计算命令长度
val cmdLength = cmdData.size val cmdLength = cmdData.size
// 创建命令包 // 创建命令包
val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC
// 帧头 // 帧头
cmdPacket[0] = BleConst.APP_REQUEST_HEADER cmdPacket[0] = BleConst.APP_REQUEST_HEADER
// 命令类型 // 命令类型
cmdPacket[1] = cmdType cmdPacket[1] = cmdType
// 命令长度 // 命令长度
cmdPacket[2] = cmdLength.toByte() cmdPacket[2] = cmdLength.toByte()
// 填充命令数据(如果有) // 填充命令数据(如果有)
if (cmdLength > 0) { if (cmdLength > 0) {
System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength) System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength)
} }
// 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身) // 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身)
val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1) val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1)
val crc = crc8Maxim(crcPayload, crcPayload.size) val crc = crc8Maxim(crcPayload, crcPayload.size)
// 设置CRC // 设置CRC
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
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false return bluetoothGatt?.writeCharacteristic(writeChar) ?: false
@ -1091,7 +1186,7 @@ object BleService {
} }
} }
} }
/** /**
* 向所有回调监听器分发连接状态变化 * 向所有回调监听器分发连接状态变化
*/ */
@ -1104,7 +1199,7 @@ object BleService {
} }
} }
} }
/** /**
* 向所有回调监听器分发音频数据 * 向所有回调监听器分发音频数据
*/ */
@ -1117,7 +1212,7 @@ object BleService {
} }
} }
} }
/** /**
* 向所有回调监听器分发唤醒信号 * 向所有回调监听器分发唤醒信号
*/ */
@ -1130,7 +1225,7 @@ object BleService {
} }
} }
} }
/** /**
* 向所有回调监听器分发设备信息 * 向所有回调监听器分发设备信息
*/ */

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

@ -12,13 +12,13 @@ import com.yunqiinnovation.deepvoice_utils.FileLogger
*/ */
object ChatAgent : BleService.Callback, AgentServiceListener { object ChatAgent : BleService.Callback, AgentServiceListener {
private const val TAG = "ChatAgent" private const val TAG = "ChatAgent"
// 是否已初始化 // 是否已初始化
private var isInitialized = false private var isInitialized = false
// 是否已注册BLE回调 // 是否已注册BLE回调
private var isRegisteredToBle = false private var isRegisteredToBle = false
/** /**
* 初始化语音代理 * 初始化语音代理
* @param context 上下文 * @param context 上下文
@ -26,16 +26,16 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
*/ */
fun initialize(context: Context, config: Map<String, Any>): Boolean { fun initialize(context: Context, config: Map<String, Any>): Boolean {
if (isInitialized) return true if (isInitialized) return true
try { try {
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
} }
// 2. 初始化BleService并注册回调 // 2. 初始化BleService并注册回调
if (BleService.initialize(context)) { if (BleService.initialize(context)) {
if (BleService.addCallback(this)) { if (BleService.addCallback(this)) {
@ -48,7 +48,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
FileLogger.e(TAG, "BLE服务初始化失败") FileLogger.e(TAG, "BLE服务初始化失败")
return false return false
} }
isInitialized = true isInitialized = true
FileLogger.d(TAG, "语音代理初始化成功") FileLogger.d(TAG, "语音代理初始化成功")
return true return true
@ -57,7 +57,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
return false return false
} }
} }
/** /**
* 手动启动语音识别 * 手动启动语音识别
*/ */
@ -66,31 +66,31 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
FileLogger.e(TAG, "语音代理未初始化") FileLogger.e(TAG, "语音代理未初始化")
return false return false
} }
return AgentService.startRecognition() return AgentService.startRecognition(true)
} }
/** /**
* 停止语音识别 * 停止语音识别
*/ */
fun stopListening() { fun stopListening() {
AgentService.stopRecognition() AgentService.stopRecognition()
} }
/** /**
* 处理文本输入 * 处理文本输入
*/ */
fun processTextInput(text: String, speakResponse: Boolean = false): Boolean { fun processTextInput(text: String, speakResponse: Boolean = false): Boolean {
return AgentService.processTextInput(text, speakResponse) return AgentService.processTextInput(text, speakResponse)
} }
/** /**
* 停止TTS * 停止TTS
*/ */
fun stopTts() { fun stopTts() {
AgentService.stopTts() AgentService.stopTts()
} }
/** /**
* 清理资源 * 清理资源
*/ */
@ -100,21 +100,21 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
BleService.removeCallback(this) BleService.removeCallback(this)
isRegisteredToBle = false isRegisteredToBle = false
} }
// 释放AgentService资源 // 释放AgentService资源
AgentService.dispose() AgentService.dispose()
isInitialized = false isInitialized = false
FileLogger.d(TAG, "语音代理资源已释放") FileLogger.d(TAG, "语音代理资源已释放")
} }
//============================================================================================ //============================================================================================
// BleService.Callback 接口实现 // BleService.Callback 接口实现
//============================================================================================ //============================================================================================
override fun onScanResult(devices: List<Map<String, Any>>) { override fun onScanResult(devices: List<Map<String, Any>>) {
// 不处理扫描结果 // 不处理扫描结果
} }
override fun onConnectionStateChanged(state: Int) { override fun onConnectionStateChanged(state: Int) {
// 可选:处理连接状态变化 // 可选:处理连接状态变化
if (state == BleConst.STATE_CONNECTED) { if (state == BleConst.STATE_CONNECTED) {
@ -125,18 +125,19 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
// stopListening() // stopListening()
} }
} }
override fun onAudioDataReceived(data: ByteArray) { override fun onAudioDataReceived(data: ByteArray) {
AgentService.azureAsrHelper?.pushAudioData(data)
// 可选:处理音频数据 // 可选:处理音频数据
} }
/** /**
* 处理唤醒信号 * 处理唤醒信号
* 在收到唤醒信号时启动语音识别 * 在收到唤醒信号时启动语音识别
*/ */
override fun onWakeupSignalReceived() { override fun onWakeupSignalReceived() {
FileLogger.d(TAG, "收到唤醒信号,启动语音识别") FileLogger.d(TAG, "收到唤醒信号,启动语音识别")
// 停止当前TTS,避免冲突 // 停止当前TTS,避免冲突
AgentService.interruptCurrentResponse() AgentService.interruptCurrentResponse()
AgentService.speakText("我在!") AgentService.speakText("我在!")
@ -148,11 +149,11 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
startListening() startListening()
} }
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) { override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {
// 不处理设备信息 // 不处理设备信息
} }
//============================================================================================ //============================================================================================
// AgentServiceListener 接口实现 // AgentServiceListener 接口实现
//============================================================================================ //============================================================================================

18
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)) {
@ -378,7 +380,7 @@ class BleService(private val context: Context) {
opusPlugin?.handleDecodeRequest(value) opusPlugin?.handleDecodeRequest(value)
} }
// 判断是否是协议格式响应 // 判断是否是协议格式响应
if (value.isNotEmpty() && value[0].toInt() and 0xFF == 0xA5) { if (value.isNotEmpty() && value[0].toInt() and 0xFF == 0xA5) {

695
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 {
interface CallbackOpus {
fun onAudioDataReceived1(data: ByteArray)
fun onAudioDataReceivedOpus(data: ByteArray)
}
companion object {
private const val TAG = "JlOpusPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events"
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
streamSink = events
}
override fun onCancel(arguments: Any?) {
streamSink = null
}
})
// 初始化OpusManager
try {
opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功")
} catch (e: OpusException) {
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) { companion object {
if (opusManager == null) { private const val TAG = "JlOpusPlugin"
result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null) private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus"
return private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events"
} }
when (call.method) { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
"initOpusDecoder" -> {
result.success(opusManager != null) context = flutterPluginBinding.applicationContext
} channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
"decodeOpusFile" -> { channel.setMethodCallHandler(this)
val inPath = call.argument<String>("inPath")
val outPath = call.argument<String>("outPath") eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1 eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
val sampleRate = call.argument<Int>("sampleRate") ?: 16000 override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
val packetSize = call.argument<Int>("packetSize") ?: 40 streamSink = events
}
if (inPath == null || outPath == null) {
result.error("INVALID_ARGS", "输入或输出路径不能为空", null) override fun onCancel(arguments: Any?) {
return streamSink = null
}
// 确保输入文件存在
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)
}
})
}
"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()
}
// 创建解码参数
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)
}
}) })
}
"stopDecodeStream" -> { // 初始化OpusManager
if (opusManager?.isDecodeStream == true) { try {
opusManager?.stopDecodeStream() opusManager = OpusManager()
result.success(true) Log.d(TAG, "OpusManager初始化成功")
} catch (e: OpusException) {
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 { } else {
result.success(false) // 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
} }
} }
"writeAudioStream" -> {
val data = call.argument<ByteArray>("data") // 在主线程上安全返回结果
if (data == null) { private fun sendResultOnMainThread(result: Result, value: Any?) {
result.error("INVALID_ARGS", "音频数据不能为空", null) if (Looper.myLooper() == Looper.getMainLooper()) {
return // 如果已经在主线程,直接返回
result.success(value)
} else {
// 否则切换到主线程
mainHandler.post {
result.success(value)
}
} }
}
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(data) // 在主线程上安全返回错误
result.success(true) private fun sendErrorOnMainThread(
result: Result,
errorCode: String,
errorMessage: String,
errorDetails: Any?
) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else { } else {
result.error("NOT_DECODING", "当前没有处于解码状态", null) // 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
} }
} }
"isDecoding" -> {
result.success(opusManager?.isDecodeStream == true) override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
} if (opusManager == null) {
"dispose" -> { result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null)
opusManager?.let { return
if (it.isDecodeStream) { }
it.stopDecodeStream()
} when (call.method) {
it.release() "initOpusDecoder" -> {
result.success(opusManager != null)
}
"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
)
}
})
}
"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()
}
// 创建解码参数
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
)
}
})
}
"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) {
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