diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt new file mode 100644 index 000000000..35a241112 --- /dev/null +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt @@ -0,0 +1,297 @@ +package com.yunqiinnovation.ble_service + +import android.Manifest +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCharacteristic +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.annotation.RequiresPermission +import java.util.* + +/** + * BLE指令发送器类:负责处理蓝牙设备的指令发送和队列管理 + * 功能包括: + * 1. 指令队列管理 + * 2. 指令封装和CRC校验 + * 3. 指令发送和重试机制 + * 4. 响应超时处理 + */ +class BleCommandSender { + + companion object { + private const val TAG = "BleCommandSender" + private const val REPLY_TIMEOUT_MS = 1000L // 响应超时时间1秒 + } + + // ====================================================================================================== + // 成员变量 + // ====================================================================================================== + + /** 指令队列 */ + private val commandQueue: Queue = LinkedList() + + /** 是否正在等待回复 */ + var isWaitingReply = true + private set + + /** 回复的指令类型 */ + var replyCommandType = 0xff + private set + + /** 响应超时处理器 */ + private val replyTimeoutHandler = Handler(Looper.getMainLooper()) + + /** 响应超时回调 */ + private val replyTimeoutRunnable = Runnable { + if (!isWaitingReply) { + Log.w(TAG, "命令响应超时,自动重置 isWaitingReply 为 true") + isWaitingReply = true + processNextCommand() + } + } + + /** 指令发送状态回调接口 */ + interface CommandSenderCallback { + /** + * 检查连接状态 + * @return 是否已连接 + */ + fun isConnected(): Boolean + + /** + * 获取写入特征 + * @return 写入特征对象 + */ + fun getWriteCharacteristic(): BluetoothGattCharacteristic? + + /** + * 获取GATT连接 + * @return GATT连接对象 + */ + fun getBluetoothGatt(): BluetoothGatt? + } + + private var callback: CommandSenderCallback? = null + + // ====================================================================================================== + // 公共方法 + // ====================================================================================================== + + /** + * 设置回调接口 + * @param callback 回调接口实现 + */ + fun setCallback(callback: CommandSenderCallback) { + this.callback = callback + } + + /** + * 发送命令到设备 - 通用方法 + * @param cmdType 命令类型 + * @param cmdData 命令数据 + * @return 是否成功发送 + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean { + val callback = this.callback ?: run { + Log.e(TAG, "发送命令失败: 回调接口未设置") + return false + } + + if (!callback.isConnected()) { + Log.e(TAG, "发送命令失败: 设备未连接") + return false + } + + try { + // 创建命令包 + val cmdPacket = createCommandPacket(cmdType, cmdData) + + // 记录命令详情 + logCommandDetails(cmdType, cmdData, cmdPacket) + + // 获取写入特征 + val writeChar = callback.getWriteCharacteristic() ?: run { + Log.e(TAG, "发送命令失败: 写入特征未找到") + return false + } + + // 设置数据并发送 + writeChar.value = cmdPacket + + return if (isWaitingReply) { + // 直接发送 + val gatt = callback.getBluetoothGatt() + val isSuccess = gatt?.writeCharacteristic(writeChar) == true + if (isSuccess) { + isWaitingReply = false + startReplyTimeout() + true + } else { + Log.e(TAG, "发送命令失败: 写入特征失败") + false + } + } else { + // 加入队列等待发送 + commandQueue.offer(cmdPacket) + Log.d(TAG, "命令已加入队列,当前队列长度: ${commandQueue.size}") + true + } + + } catch (e: Exception) { + Log.e(TAG, "发送命令异常: ${e.message}", e) + return false + } + } + + /** + * 处理命令响应,重置等待状态并处理下一个命令 + * @param responseCommandType 响应的命令类型 + */ + fun onCommandResponse(responseCommandType: Int) { + isWaitingReply = true + replyCommandType = responseCommandType + replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable) + processNextCommand() + } + + /** + * 清空命令队列 + */ + fun clearCommandQueue() { + commandQueue.clear() + Log.d(TAG, "命令队列已清空") + } + + /** + * 获取当前队列长度 + * @return 队列中待发送的命令数量 + */ + fun getQueueSize(): Int = commandQueue.size + + /** + * 重置发送状态 + */ + fun reset() { + isWaitingReply = true + replyCommandType = 0xff + replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable) + clearCommandQueue() + Log.d(TAG, "指令发送器已重置") + } + + // ====================================================================================================== + // 私有方法 + // ====================================================================================================== + + /** + * 创建命令包 + * @param cmdType 命令类型 + * @param cmdData 命令数据 + * @return 完整的命令包(包含帧头、命令、长度、数据、CRC) + */ + private fun createCommandPacket(cmdType: Byte, cmdData: ByteArray): ByteArray { + val cmdLength = cmdData.size + val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC + + // 帧头 + cmdPacket[0] = BleConst.APP_REQUEST_HEADER + // 命令类型 + cmdPacket[1] = cmdType + // 命令长度 + cmdPacket[2] = cmdLength.toByte() + + // 填充命令数据(如果有) + if (cmdLength > 0) { + System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength) + } + + // 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身) + val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1) + val crc = calculateCrc8Maxim(crcPayload, crcPayload.size) + + // 设置CRC + cmdPacket[3 + cmdLength] = crc + + return cmdPacket + } + + /** + * 记录命令详情 + * @param cmdType 命令类型 + * @param cmdData 命令数据 + * @param cmdPacket 完整命令包 + */ + private fun logCommandDetails(cmdType: Byte, cmdData: ByteArray, cmdPacket: ByteArray) { + val crc = cmdPacket[cmdPacket.size - 1] + Log.d( + TAG, + "发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=${cmdData.size}, " + + "数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " + + "CRC=0x${(crc.toInt() and 0xFF).toString(16)}" + ) + } + + /** + * 处理队列中的下一个命令 + */ + private fun processNextCommand() { + if (commandQueue.isEmpty()) { + return + } + + val callback = this.callback ?: return + val nextCmd = commandQueue.poll() ?: return + + val writeChar = callback.getWriteCharacteristic() ?: run { + Log.e(TAG, "处理下一个命令失败: 写入特征未找到") + return + } + + val gatt = callback.getBluetoothGatt() ?: run { + Log.e(TAG, "处理下一个命令失败: GATT连接未找到") + return + } + + writeChar.value = nextCmd + val isSuccess = gatt.writeCharacteristic(writeChar) + if (isSuccess) { + isWaitingReply = false + startReplyTimeout() + Log.d(TAG, "发送队列中的下一个命令,剩余队列长度: ${commandQueue.size}") + } else { + Log.e(TAG, "发送队列中的命令失败") + } + } + + /** + * 启动响应超时计时器 + */ + private fun startReplyTimeout() { + replyTimeoutHandler.postDelayed(replyTimeoutRunnable, REPLY_TIMEOUT_MS) + } + + /** + * 计算CRC8/MAXIM校验值 + * @param data 要校验的数据 + * @param length 数据长度 + * @return CRC8校验值 + */ + private fun calculateCrc8Maxim(data: ByteArray, length: Int): Byte { + var crc = 0 + val polynomial = 0x31 + + for (i in 0 until length) { + crc = crc xor (data[i].toInt() and 0xFF) + for (j in 0 until 8) { + if (crc and 0x80 != 0) { + crc = ((crc shl 1) xor polynomial) and 0xFF + } else { + crc = (crc shl 1) and 0xFF + } + } + } + return crc.toByte() + } +} \ No newline at end of file diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 12bf9f64e..f7e688497 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -68,7 +68,6 @@ object BleService { // 上下文 private lateinit var context: Context - private val commandQueue: Queue = LinkedList() // 蓝牙相关组件 private lateinit var bluetoothManager: BluetoothManager @@ -141,6 +140,9 @@ object BleService { // 初始化状态 private var isInitialized = false + // 指令发送器 + private val commandSender = BleCommandSender() + /** * 检查是否有蓝牙扫描权限 */ @@ -177,14 +179,7 @@ object BleService { } } - private val replyTimeoutHandler = Handler(Looper.getMainLooper()) - private val replyTimeoutRunnable = Runnable { - if (!isReply) { - Log.w(TAG, "命令响应超时,自动重置 isReply 为 true") - isReply = true - processNextCommand() - } - } + /** * 初始化BLE服务 @@ -222,6 +217,10 @@ object BleService { Log.e(TAG, "OpusManager初始化失败: ${e.message}", e) // 根据需要决定是否因为Opus初始化失败而返回false } + + // 初始化指令发送器 + initializeCommandSender() + isInitialized = true return true } catch (e: Exception) { @@ -244,6 +243,25 @@ object BleService { return false } + /** + * 初始化指令发送器 + */ + private fun initializeCommandSender() { + commandSender.setCallback(object : BleCommandSender.CommandSenderCallback { + override fun isConnected(): Boolean { + return checkConn() + } + + override fun getWriteCharacteristic(): BluetoothGattCharacteristic? { + return writeChar + } + + override fun getBluetoothGatt(): BluetoothGatt? { + return bluetoothGatt + } + }) + } + /** * 移除回调监听器 * @param callback 要移除的回调监听器 @@ -604,7 +622,7 @@ object BleService { Log.e(TAG, "关闭GATT连接异常: ${e.message}", e) } isReply = true; - commandQueue.clear() + commandSender.clearCommandQueue() bluetoothGatt = null notifyChar = null writeChar = null @@ -905,6 +923,8 @@ startBytesStatistics() "rawData" to byteArrayOf() ) notifyDeviceInfoReceived(cmdType, resultMap) + // 通知指令发送器处理响应 + commandSender.onCommandResponse(cmdType) return } @@ -946,10 +966,8 @@ startBytesStatistics() } }" ) - isReply = true - cmdReplyType = cmdType - replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable) // 移除超时回调 - processNextCommand() + // 通知指令发送器处理响应 + commandSender.onCommandResponse(cmdType) // 根据命令类型处理不同的响应 when (cmdType) { BleConst.CMD_GET_VERSION -> { @@ -1601,201 +1619,185 @@ startBytesStatistics() } } // ====================================================================================================== - // 公开的命令接口 + // 设备控制方法 - 使用新的指令发送器 // ====================================================================================================== + /** - * 获取耳机版本号 + * 获取设备版本信息 + * @return 是否成功发送命令 */ fun getDeviceVersion(): Boolean { - Log.i(TAG, "获取耳机版本号...") - return sendCommand(BleConst.CMD_GET_VERSION.toByte(), byteArrayOf(0xFF.toByte())) + Log.i(TAG, "获取设备版本信息...") + return commandSender.sendCommand(BleConst.CMD_GET_VERSION.toByte()) } /** * 获取产品ID + * @return 是否成功发送命令 */ fun getProductId(): Boolean { - Log.i(TAG, "获取耳机产品ID...") - return sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte(), byteArrayOf(0xFF.toByte())) + Log.i(TAG, "获取产品ID...") + return commandSender.sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte()) } /** - * 获取耳机颜色ID + * 获取颜色ID + * @return 是否成功发送命令 */ fun getColorId(): Boolean { - Log.i(TAG, "获取耳机颜色ID...") - return sendCommand(BleConst.CMD_GET_COLOR_ID.toByte(), byteArrayOf(0xFF.toByte())) + Log.i(TAG, "获取颜色ID...") + return commandSender.sendCommand(BleConst.CMD_GET_COLOR_ID.toByte()) } /** - * 获取耳机电量信息 + * 获取电量信息 + * @return 是否成功发送命令 */ fun getBatteryInfo(): Boolean { - Log.i(TAG, "获取耳机电量信息...") - return sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte(), byteArrayOf(0xFF.toByte())) + Log.i(TAG, "获取电量信息...") + return commandSender.sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte()) } /** - * 控制编解码 - 关闭编解码 + * 关闭编解码 + * @return 是否成功发送命令 */ fun closeCodec(): Boolean { Log.i(TAG, "关闭编解码...") - return sendCommand( - BleConst.CMD_CONTROL_CODEC.toByte(), - byteArrayOf( - BleConst.CODEC_CONTROL_CLOSE.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte() - ) // 或其他默认声道 + val cmdData = byteArrayOf( + BleConst.CODEC_CONTROL_CLOSE.toByte(), + BleConst.AUDIO_CHANNEL_STEREO.toByte() ) + return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData) } /** - * 控制编解码 - 打开解码 + * 打开解码器 + * @param channelMode 声道模式 + * @return 是否成功发送命令 */ - fun openDecoder(): Boolean { - // val opusStarted = startOpusStreamDecoding( - // hasHeader = false, // BLE传输的Opus数据通常无文件头 - // channel = 1, // 单声道 - // sampleRate = 16000,// Opus推荐16kHz或更高 - // packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM) - // // packetSize for Opus is typically in ms, e.g. 20, 40, 60. - // // The library might expect this in a different unit or derive it. - // // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. - // ) + fun openDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean { + Log.i(TAG, "打开解码器...") startOpusStreamDecoding(false, 1, 16000, 40) - - Log.i(TAG, "打开解码0xA1") - return sendCommand( - BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( - BleConst.CODEC_CONTROL_DECODE_ON.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte() - ) + val cmdData = byteArrayOf( + BleConst.CODEC_CONTROL_DECODE_ON.toByte(), + channelMode.toByte() ) + return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData) } /** - * 控制编解码 - 打开解码 + * 打开A2DP解码器 + * @param channelMode 声道模式 + * @return 是否成功发送命令 */ - fun openA2DPDecoder(): Boolean { - Log.i(TAG, "打开编码 0xA2") - + fun openA2DPDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean { + Log.i(TAG, "打开A2DP解码器...") startOpusEncodeStream() - //双声道 80字节 startOpusStreamDecoding(false, 2, 16000, 80) - // Log.i(TAG, "打开解码...") - return sendCommand( - BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( - BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte() - ) + val cmdData = byteArrayOf( + BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(), + channelMode.toByte() ) + return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData) } + /** - * 控制编解码 - 打开解码 + * 打开通话录音解码器 + * @param channelMode 声道模式 + * @return 是否成功发送命令 */ - fun openCallRecordDecoder(): Boolean { - Log.i(TAG, "打开编码 0xA3") - //双声道 80字节 + fun openCallRecordDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean { + Log.i(TAG, "打开通话录音解码器...") startOpusStreamDecoding(false, 2, 16000, 80) - // Log.i(TAG, "打开解码...") - return sendCommand( - BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( - BleConst.CODEC_CONTROL_CALL_RECORD_PLAY.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte() - ) + val cmdData = byteArrayOf( + BleConst.CODEC_CONTROL_CALL_RECORD_PLAY.toByte(), + channelMode.toByte() ) + return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData) } /** - * 控制编解码 - A2DP播放 + * A2DP播放 + * @return 是否成功发送命令 */ fun playA2DP(): Boolean { - Log.i(TAG, "切换至A2DP播放模式...") - return sendCommand(BleConst.CMD_PLAY_A2DP.toByte()) + Log.i(TAG, "A2DP播放...") + return commandSender.sendCommand(BleConst.CMD_PLAY_A2DP.toByte()) } /** - * 控制编解码 - 打开编码 + * 打开编码器 + * @param channelMode 声道模式 + * @return 是否成功发送命令 */ - fun openEncoder(): Boolean { - Log.i(TAG, "打开编码0xB1") - + fun openEncoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean { + Log.i(TAG, "打开编码器...") startOpusStreamDecoding(false, 1, 16000, 40) - // // 目前仅发送命令通知设备开始编码。 - // return sendCommand( - // BleConst.CMD_CONTROL_CODEC.toByte(), - // byteArrayOf( - // BleConst.CODEC_CONTROL_ENCODE_ON.toByte(), - // BleConst.AUDIO_CHANNEL_RIGHT.toByte() // 或设备期望的声道 - // ) - // ) - return true + val cmdData = byteArrayOf( + BleConst.CODEC_CONTROL_ENCODE_ON.toByte(), + channelMode.toByte() + ) + return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData) } - /** * 设置声道模式 - * @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO + * @param channelMode 声道模式 + * @return 是否成功发送命令 */ fun setChannelMode(channelMode: Int): Boolean { - when (channelMode) { - BleConst.AUDIO_CHANNEL_LEFT -> Log.i(TAG, "设置左声道模式...") - BleConst.AUDIO_CHANNEL_RIGHT -> Log.i(TAG, "设置右声道模式...") - BleConst.AUDIO_CHANNEL_STEREO -> Log.i(TAG, "设置立体声模式...") - else -> { - Log.e(TAG, "无效的声道模式: $channelMode") - return false - } - } - - // 高级控制:设置命令类型、长度和两个参数 - return sendCommand( - BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( - 0xA1.toByte(), // 状态码 - 使用解码状态 - channelMode.toByte() // 声道模式 - ) + Log.i(TAG, "设置声道模式: $channelMode") + val cmdData = byteArrayOf( + BleConst.CODEC_CONTROL_ENCODE_ON.toByte(), + channelMode.toByte() ) + return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData) } /** * 音量增大 + * @return 是否成功发送命令 */ fun volumeUp(): Boolean { Log.i(TAG, "设备音量增加...") - return sendCommand(BleConst.CMD_VOLUME_UP.toByte()) + return commandSender.sendCommand(BleConst.CMD_VOLUME_UP.toByte()) } /** * 音量减小 + * @return 是否成功发送命令 */ fun volumeDown(): Boolean { Log.i(TAG, "设备音量减小...") - return sendCommand(BleConst.CMD_VOLUME_DOWN.toByte()) + return commandSender.sendCommand(BleConst.CMD_VOLUME_DOWN.toByte()) } // ====================================================================================================== - // 工具方法 + // 连接状态和工具方法 // ====================================================================================================== + /** - * 检查是否已连接 + * 检查连接状态 + * @return 是否已连接 */ - fun isConnected(): Boolean { - return connectionState.value == BleConst.STATE_CONNECTED + private fun checkConn(): Boolean { + return bluetoothGatt != null && + connectionState.value == BleConst.STATE_CONNECTED && + writeChar != null } /** - * 连接状态检查 + * 是否已连接 + * @return 连接状态 */ - private fun checkConn(): Boolean = - bluetoothGatt != null && writeChar != null && callWriteChar != null && - connectionState.value == BleConst.STATE_CONNECTED + fun isConnected(): Boolean { + return connectionState.value == BleConst.STATE_CONNECTED + } /** - * 获取已连接设备地址 + * 获取已连接设备的地址 + * @return 设备地址,未连接时返回null */ fun getConnectedDeviceAddress(): String? { return bluetoothGatt?.device?.address @@ -1803,6 +1805,7 @@ startBytesStatistics() /** * 获取蓝牙适配器 + * @return 蓝牙适配器实例 */ fun getBluetoothAdapter(): BluetoothAdapter? { return bluetoothAdapter @@ -1813,129 +1816,38 @@ startBytesStatistics() // 资源清理 // ====================================================================================================== /** - * 清理所有资源 + * 清理资源 */ fun cleanup() { + Log.i(TAG, "清理BLE服务资源...") + + // 重置指令发送器 + commandSender.reset() + + // 停止扫描 stopScan() - scanHandler.removeCallbacksAndMessages(null) - disconnectGatt() - // 停止音频队列处理线程 - audioQueueProcessorThread?.interrupt() - audioQueueProcessorThread = null - audioDataQueue.clear() - // 停止音频发送线程 + // 断开连接 + disconnect() + + // 清理回调 + clearCallbacks() + + // 停止音频处理 stopAudioSendThread() - - // 清理音频数据缓存 - synchronized(audioDataBuffer) { - audioDataBuffer.clear() - Log.d(TAG, "已清理音频数据缓存") - } - - Log.d(TAG, "音频解码线程和队列处理线程已清理") - - // 释放OpusManager - stopOpusEncodeStream() stopOpusStreamDecoding() - opusManager?.release() - opusManager = null - option = null - Log.d(TAG, "OpusManager已释放") - isInitialized = false // 标记为未初始化 - } - - - // ====================================================================================================== - // 协议命令实现 - // ====================================================================================================== - /** - * 发送命令到设备 - 通用方法 - * @param cmdType 命令类型 - * @param cmdData 命令数据 - * @return 是否成功发送 - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean { - if (!checkConn()) { - Log.e(TAG, "发送命令失败: 设备未连接") - return false - } - - try { - // 计算命令长度 - val cmdLength = cmdData.size - - // 创建命令包 - val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC - - // 帧头 - cmdPacket[0] = BleConst.APP_REQUEST_HEADER - // 命令类型 - cmdPacket[1] = cmdType - // 命令长度 - cmdPacket[2] = cmdLength.toByte() - - // 填充命令数据(如果有) - if (cmdLength > 0) { - System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength) - } - - // 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身) - val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1) - val crc = crc8Maxim(crcPayload, crcPayload.size) - - // 设置CRC - cmdPacket[3 + cmdLength] = crc - - // 记录命令详情 - Log.d( - TAG, - "发送命令: 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 - // 加入队列并触发发送 - - if (isReply) { - val isSuccess = bluetoothGatt?.writeCharacteristic(writeChar) - if (isSuccess == true) { - isReply = false - replyTimeoutHandler.postDelayed(replyTimeoutRunnable, 1000) // 设置1秒超时 - return true - } else { - commandQueue.poll() - return false - } - } else { - commandQueue.offer(cmdPacket) - } - return true - - } catch (e: Exception) { - Log.e(TAG, "发送命令异常: ${e.message}", e) - return false - } + stopOpusEncodeStream() + + isInitialized = false + Log.i(TAG, "BLE服务资源清理完成") } - private fun processNextCommand() { - if (!commandQueue.isEmpty()) { - val nextCmd = commandQueue.poll() - writeChar!!.value = nextCmd - val isSuccess = bluetoothGatt?.writeCharacteristic(writeChar) - if (isSuccess == true) { - isReply = false - replyTimeoutHandler.postDelayed(replyTimeoutRunnable, 1000) // 设置1秒超时 - Log.d(TAG, "发送队列中的下一个命令") - } - } - } /** * 计算CRC8/MAXIM校验值 + * @param data 要校验的数据 + * @param length 数据长度 + * @return CRC8校验值 */ private fun crc8Maxim(data: ByteArray, length: Int): Byte { var crc = 0