|
|
|
@ -128,6 +128,11 @@ object BleService { |
|
|
|
private val MAX_RETRY_COUNT = 1 // 最大重试次数 |
|
|
|
private val RETRY_DELAY = 30L // 重试延迟时间(毫秒) |
|
|
|
|
|
|
|
// MTU相关变量 |
|
|
|
private var currentMtu = 23 // 默认MTU值 |
|
|
|
private var maxMtu = 517 // 最大MTU值 (512 + 5字节头部) |
|
|
|
private var isWaitingForWriteCallback = false // 是否正在等待写入回调 |
|
|
|
|
|
|
|
// 初始化状态 |
|
|
|
private var isInitialized = false |
|
|
|
|
|
|
|
@ -859,6 +864,9 @@ object BleService { |
|
|
|
|
|
|
|
// 设置主服务的通知 |
|
|
|
setupNotifications(g, notifyChar) |
|
|
|
|
|
|
|
// 在服务发现完成后,请求最大MTU |
|
|
|
requestMaxMtu(g) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
@ -915,10 +923,10 @@ object BleService { |
|
|
|
override fun onCharacteristicWrite( |
|
|
|
g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int |
|
|
|
) { |
|
|
|
Log.i( |
|
|
|
TAG, |
|
|
|
"写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}" |
|
|
|
) |
|
|
|
Log.i( |
|
|
|
TAG, |
|
|
|
"写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}" |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onDescriptorWrite( |
|
|
|
@ -927,6 +935,22 @@ object BleService { |
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) |
|
|
|
Log.i(TAG, "通知描述符写入成功: ${d.characteristic.uuid}") |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* MTU变化回调 - 处理MTU协商结果 |
|
|
|
*/ |
|
|
|
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) { |
|
|
|
super.onMtuChanged(gatt, mtu, status) |
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
currentMtu = mtu |
|
|
|
Log.i(TAG, "MTU协商成功: $mtu 字节") |
|
|
|
// 更新音频块大小,预留5字节的ATT头部 |
|
|
|
val newChunkSize = (mtu - 5).coerceAtMost(512) |
|
|
|
Log.i(TAG, "更新音频块大小为: $newChunkSize 字节") |
|
|
|
} else { |
|
|
|
Log.w(TAG, "MTU协商失败: status=$status,使用默认MTU: $currentMtu") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -943,6 +967,23 @@ object BleService { |
|
|
|
desc?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE |
|
|
|
g.writeDescriptor(desc) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 请求最大MTU值 |
|
|
|
* @param gatt BluetoothGatt实例 |
|
|
|
*/ |
|
|
|
private fun requestMaxMtu(gatt: BluetoothGatt) { |
|
|
|
try { |
|
|
|
val result = gatt.requestMtu(maxMtu) |
|
|
|
if (result) { |
|
|
|
Log.i(TAG, "请求MTU $maxMtu 成功") |
|
|
|
} else { |
|
|
|
Log.w(TAG, "请求MTU $maxMtu 失败") |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "请求MTU异常: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 处理接收到的音频数据 |
|
|
|
@ -1039,9 +1080,9 @@ object BleService { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 启动音频数据发送线程 |
|
|
|
* 启动音频数据发送线程(优化版本) |
|
|
|
*/ |
|
|
|
private fun startAudioSendThread() { |
|
|
|
private fun startAudioSendThread() { |
|
|
|
if (audioSendThread?.isAlive == true) { |
|
|
|
Log.d(TAG, "音频发送线程已在运行") |
|
|
|
return |
|
|
|
@ -1056,11 +1097,9 @@ object BleService { |
|
|
|
// 从队列中取出音频数据块 |
|
|
|
val audioChunk = audioSendQueue.poll(100, TimeUnit.MILLISECONDS) |
|
|
|
|
|
|
|
if (audioChunk != null) { |
|
|
|
// 发送音频数据块 |
|
|
|
sendAudioChunk(audioChunk) |
|
|
|
|
|
|
|
// 控制发送频率,避免蓝牙缓冲区溢出 |
|
|
|
if (audioChunk != null&&!isWaitingForWriteCallback) { |
|
|
|
// 发送音频数据块并等待回调 |
|
|
|
sendAudioChunkWithCallback(audioChunk) |
|
|
|
Thread.sleep(AUDIO_SEND_INTERVAL) |
|
|
|
} |
|
|
|
} catch (e: InterruptedException) { |
|
|
|
@ -1088,96 +1127,133 @@ object BleService { |
|
|
|
Log.i(TAG, "音频发送线程已停止,队列和缓冲区已清空") |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 发送音频数据块到设备(带重发机制) |
|
|
|
/** |
|
|
|
* 发送音频数据块并等待写入回调(新方法) |
|
|
|
* @param chunk 要发送的音频数据块 |
|
|
|
* @param retryCount 当前重试次数 |
|
|
|
*/ |
|
|
|
private fun sendAudioChunk(chunk: ByteArray, retryCount: Int = 0) { |
|
|
|
private fun sendAudioChunkWithCallback(chunk: ByteArray) { |
|
|
|
try { |
|
|
|
if (callWriteChar == null || bluetoothGatt == null) { |
|
|
|
Log.e(TAG, "蓝牙连接或特征值未准备就绪") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 检查数据块大小是否超过当前MTU限制 |
|
|
|
val maxDataSize = currentMtu - 5 // 减去ATT头部5字节 |
|
|
|
if (chunk.size > maxDataSize) { |
|
|
|
Log.w(TAG, "数据块过大 (${chunk.size} > $maxDataSize),分片发送") |
|
|
|
// 分片发送 |
|
|
|
sendChunksInFragments(chunk, maxDataSize) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 在主线程中执行蓝牙写入操作 |
|
|
|
audioSendHandler.post { |
|
|
|
try { |
|
|
|
callWriteChar?.value = chunk |
|
|
|
recordfile1?.saveAudioDataAsUtf8Hex(chunk, true) |
|
|
|
val isSuccess = bluetoothGatt?.writeCharacteristic(callWriteChar) |
|
|
|
|
|
|
|
if (isSuccess == true) { |
|
|
|
recordfile1?.saveAudioDataToWav("发送成功,大小: ${chunk.size} 字节") |
|
|
|
// 累加发送成功的字节数 |
|
|
|
bytesSentInCurrentSecond += chunk.size |
|
|
|
Log.d(TAG, "成功发送音频数据块,大小: ${chunk.size} 字节") |
|
|
|
} else { |
|
|
|
recordfile1?.saveAudioDataToWav("发送失败,大小: ${chunk.size} 字节") |
|
|
|
Log.e(TAG, "发送音频数据块失败,大小: ${chunk.size} 字节") |
|
|
|
// 详细的失败原因分析 |
|
|
|
val failureReason = StringBuilder("发送音频数据块失败,大小: ${chunk.size} 字节") |
|
|
|
|
|
|
|
isWaitingForWriteCallback = true |
|
|
|
|
|
|
|
// 检查蓝牙连接状态 |
|
|
|
if (bluetoothGatt == null) { |
|
|
|
failureReason.append(" - 原因: BluetoothGatt为空") |
|
|
|
} else { |
|
|
|
// 检查连接状态 |
|
|
|
val State = bluetoothManager.getConnectionState( |
|
|
|
bluetoothGatt!!.device, |
|
|
|
BluetoothProfile.GATT |
|
|
|
) |
|
|
|
when (State) { |
|
|
|
BluetoothProfile.STATE_DISCONNECTED -> { |
|
|
|
failureReason.append(" -发送失败 原因: 设备已断开连接") |
|
|
|
} |
|
|
|
BluetoothProfile.STATE_CONNECTING -> { |
|
|
|
failureReason.append(" -发送失败 原因: 设备正在连接中") |
|
|
|
} |
|
|
|
BluetoothProfile.STATE_DISCONNECTING -> { |
|
|
|
failureReason.append(" -发送失败 原因: 设备正在断开连接") |
|
|
|
} |
|
|
|
BluetoothProfile.STATE_CONNECTED -> { |
|
|
|
// 连接正常,检查其他原因 |
|
|
|
if (callWriteChar == null) { |
|
|
|
failureReason.append(" -发送失败 原因: 写入特征值为空") |
|
|
|
} else { |
|
|
|
// 检查特征值属性 |
|
|
|
val properties = callWriteChar!!.properties |
|
|
|
if ((properties and BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 && |
|
|
|
(properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) { |
|
|
|
failureReason.append(" -发送失败 原因: 特征值不支持写入操作") |
|
|
|
} else if (chunk.size > 512) { // BLE MTU通常限制 |
|
|
|
failureReason.append(" -发送失败 原因: 数据块过大 (${chunk.size} > 512字节)") |
|
|
|
} else { |
|
|
|
failureReason.append(" -发送失败 原因: 未知错误,可能是设备忙碌或缓冲区满") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else -> { |
|
|
|
failureReason.append(" -发送失败 原因: 未知连接状态($connectionState)") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
callWriteChar?.value = chunk |
|
|
|
recordfile1?.saveAudioDataAsUtf8Hex(chunk, true) |
|
|
|
val isSuccess = bluetoothGatt?.writeCharacteristic(callWriteChar) |
|
|
|
|
|
|
|
// 检查蓝牙适配器状态 |
|
|
|
if (bluetoothAdapter?.isEnabled != true) { |
|
|
|
failureReason.append(" - 蓝牙适配器未启用") |
|
|
|
if (isSuccess == true) { |
|
|
|
isWaitingForWriteCallback = false |
|
|
|
recordfile1?.saveAudioDataToWav("发送成功,大小: ${chunk.size} 字节") |
|
|
|
bytesSentInCurrentSecond += chunk.size |
|
|
|
Log.d(TAG, "成功发送音频数据块,大小: ${chunk.size} 字节,等待写入回调...") |
|
|
|
} else { |
|
|
|
isWaitingForWriteCallback = false |
|
|
|
recordfile1?.saveAudioDataToWav("发送失败,大小: ${chunk.size} 字节") |
|
|
|
Log.e(TAG, "发送音频数据块失败,大小: ${chunk.size} 字节") |
|
|
|
analyzeWriteFailure(chunk) |
|
|
|
} |
|
|
|
|
|
|
|
Log.e(TAG, failureReason.toString()) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "发送音频数据块异常: ${e.message}", e) |
|
|
|
|
|
|
|
Log.e(TAG, "发送音频数据块异常: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "发送音频数据块异常: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 分片发送大数据块 |
|
|
|
* @param data 原始数据 |
|
|
|
* @param maxChunkSize 每片的最大大小 |
|
|
|
*/ |
|
|
|
private fun sendChunksInFragments(data: ByteArray, maxChunkSize: Int) { |
|
|
|
var offset = 0 |
|
|
|
while (offset < data.size) { |
|
|
|
val remainingSize = data.size - offset |
|
|
|
val chunkSize = minOf(remainingSize, maxChunkSize) |
|
|
|
val fragment = data.copyOfRange(offset, offset + chunkSize) |
|
|
|
|
|
|
|
// 递归调用发送单个片段 |
|
|
|
sendAudioChunkWithCallback(fragment) |
|
|
|
|
|
|
|
offset += chunkSize |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 分析写入失败的原因(从原sendAudioChunk方法提取) |
|
|
|
* @param chunk 失败的数据块 |
|
|
|
*/ |
|
|
|
private fun analyzeWriteFailure(chunk: ByteArray) { |
|
|
|
val failureReason = StringBuilder("发送音频数据块失败,大小: ${chunk.size} 字节") |
|
|
|
|
|
|
|
// 检查蓝牙连接状态 |
|
|
|
if (bluetoothGatt == null) { |
|
|
|
failureReason.append(" - 原因: BluetoothGatt为空") |
|
|
|
} else { |
|
|
|
// 检查连接状态 |
|
|
|
val state = bluetoothManager.getConnectionState( |
|
|
|
bluetoothGatt!!.device, |
|
|
|
BluetoothProfile.GATT |
|
|
|
) |
|
|
|
when (state) { |
|
|
|
BluetoothProfile.STATE_DISCONNECTED -> { |
|
|
|
failureReason.append(" -发送失败 原因: 设备已断开连接") |
|
|
|
} |
|
|
|
BluetoothProfile.STATE_CONNECTING -> { |
|
|
|
failureReason.append(" -发送失败 原因: 设备正在连接中") |
|
|
|
} |
|
|
|
BluetoothProfile.STATE_DISCONNECTING -> { |
|
|
|
failureReason.append(" -发送失败 原因: 设备正在断开连接") |
|
|
|
} |
|
|
|
BluetoothProfile.STATE_CONNECTED -> { |
|
|
|
// 连接正常,检查其他原因 |
|
|
|
if (callWriteChar == null) { |
|
|
|
failureReason.append(" -发送失败 原因: 写入特征值为空") |
|
|
|
} else { |
|
|
|
// 检查特征值属性 |
|
|
|
val properties = callWriteChar!!.properties |
|
|
|
if ((properties and BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 && |
|
|
|
(properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) { |
|
|
|
failureReason.append(" -发送失败 原因: 特征值不支持写入操作") |
|
|
|
} else if (chunk.size > currentMtu - 5) { |
|
|
|
failureReason.append(" -发送失败 原因: 数据块过大 (${chunk.size} > ${currentMtu - 5}字节)") |
|
|
|
} else { |
|
|
|
failureReason.append(" -发送失败 原因: 未知错误,可能是设备忙碌或缓冲区满") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else -> { |
|
|
|
failureReason.append(" -发送失败 原因: 未知连接状态($state)") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 检查蓝牙适配器状态 |
|
|
|
if (bluetoothAdapter?.isEnabled != true) { |
|
|
|
failureReason.append(" - 蓝牙适配器未启用") |
|
|
|
} |
|
|
|
|
|
|
|
Log.e(TAG, failureReason.toString()) |
|
|
|
} |
|
|
|
// ====================================================================================================== |
|
|
|
// 设备控制方法 - 使用新的指令发送器 |
|
|
|
// ====================================================================================================== |
|
|
|
@ -1386,6 +1462,40 @@ object BleService { |
|
|
|
fun getBluetoothAdapter(): BluetoothAdapter? { |
|
|
|
return bluetoothAdapter |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 获取当前MTU值 |
|
|
|
* @return 当前MTU值 |
|
|
|
*/ |
|
|
|
fun getCurrentMtu(): Int { |
|
|
|
return currentMtu |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 获取可用的数据传输大小(MTU减去头部) |
|
|
|
* @return 可用的数据传输大小 |
|
|
|
*/ |
|
|
|
fun getAvailableDataSize(): Int { |
|
|
|
return currentMtu - 5 // 减去ATT头部5字节 |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 手动请求MTU协商 |
|
|
|
* @param mtu 请求的MTU值 |
|
|
|
* @return 是否成功发起请求 |
|
|
|
*/ |
|
|
|
fun requestMtu(mtu: Int): Boolean { |
|
|
|
return bluetoothGatt?.let { gatt -> |
|
|
|
try { |
|
|
|
val result = gatt.requestMtu(mtu.coerceIn(23, 517)) |
|
|
|
Log.i(TAG, "手动请求MTU $mtu: $result") |
|
|
|
result |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "手动请求MTU异常: ${e.message}", e) |
|
|
|
false |
|
|
|
} |
|
|
|
} ?: false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
|
|