Browse Source

上传协议优化

newdev_shunjiawei
liwei1dao 5 months ago
parent
commit
45bc21108b
  1. 28
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt
  2. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  3. 34
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  4. 2
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift
  5. 51
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
  6. 498
      pubspec.lock

28
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt

@ -85,6 +85,11 @@ class BleCommandSender {
*/ */
fun notifyWakeupSignalReceived() fun notifyWakeupSignalReceived()
/**
* 通知通话休眠指令接收
*/
fun notifyCallSleepReceived()
/** /**
* 通知开始 AI 单次对话 * 通知开始 AI 单次对话
*/ */
@ -219,7 +224,7 @@ class BleCommandSender {
TAG, TAG,
"设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}" "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}"
) )
return // return
} }
} else { } else {
Log.w(TAG, "设备响应长度不足以包含CRC,跳过CRC校验") Log.w(TAG, "设备响应长度不足以包含CRC,跳过CRC校验")
@ -377,6 +382,7 @@ class BleCommandSender {
BleConst.CODEC_CONTROL_A2DP_PLAY -> "A2DP播放模式" BleConst.CODEC_CONTROL_A2DP_PLAY -> "A2DP播放模式"
BleConst.CODEC_CONTROL_CALL_RECORD_PLAY -> "通话记录播放模式" BleConst.CODEC_CONTROL_CALL_RECORD_PLAY -> "通话记录播放模式"
BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码" BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码"
BleConst.CODEC_CONTROL_CALL_Sleep -> "通话休眠"
else -> "未知状态($codecStatus)" else -> "未知状态($codecStatus)"
} }
@ -582,6 +588,26 @@ class BleCommandSender {
callback?.notifyWakeupSignalReceived() callback?.notifyWakeupSignalReceived()
} }
BleConst.CMD_CONTROL_CODEC -> {
// 设备主动上报编解码控制状态,目前只关心 CODEC_CONTROL_CALL_Sleep (0xFF)
// 帧格式: 0xCC 0x05 [len] [codecStatus, channelMode, ...] [crc]
val codecStatus =
if (notifyData.isNotEmpty()) notifyData[0].toInt() and 0xFF else -1
if (codecStatus == BleConst.CODEC_CONTROL_CALL_Sleep) {
Log.i(TAG, "收到主动上报: 通话休眠 (codecStatus=0xFF)")
callback?.notifyCallSleepReceived()
} else {
Log.d(TAG, "收到主动上报编解码控制: codecStatus=0x${codecStatus.toString(16)}")
}
val resultMap = mapOf(
"success" to true,
"command" to infoType,
"rawData" to notifyData,
"codecStatus" to codecStatus,
"isNotification" to true
)
callback?.notifyDeviceInfoReceived(infoType, resultMap)
}
BleConst.CMD_START_SINGLE_DIALOG -> { BleConst.CMD_START_SINGLE_DIALOG -> {
Log.i(TAG, "收到开始 AI 单次对话指令") Log.i(TAG, "收到开始 AI 单次对话指令")
// 接收到开始 AI 单次对话信号,打开编码器 (设备侧) // 接收到开始 AI 单次对话信号,打开编码器 (设备侧)

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

@ -121,6 +121,8 @@ object BleConst {
const val CODEC_CONTROL_A2DP_PLAY = 0xA2 const val CODEC_CONTROL_A2DP_PLAY = 0xA2
/** mic和dac(音乐或者通话远端)声音 */ /** mic和dac(音乐或者通话远端)声音 */
const val CODEC_CONTROL_CALL_RECORD_PLAY = 0xA3 const val CODEC_CONTROL_CALL_RECORD_PLAY = 0xA3
/** 通话休眠 */
const val CODEC_CONTROL_CALL_Sleep = 0xFF
/** 打开编码指令 */ /** 打开编码指令 */
const val CODEC_CONTROL_ENCODE_ON = 0xB1 const val CODEC_CONTROL_ENCODE_ON = 0xB1
/** 左声道 */ /** 左声道 */

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

@ -140,7 +140,11 @@ object BleService {
// 音频数据分块发送的常量 // 音频数据分块发送的常量
private val AUDIO_CHUNK_SIZE = 120 // 每次发送120字节 private val AUDIO_CHUNK_SIZE = 120 // 每次发送120字节
private val AUDIO_SEND_INTERVAL = 60L // 发送间隔60ms private const val AUDIO_SEND_INTERVAL_NORMAL = 60L // 正常发送间隔60ms
private const val AUDIO_SEND_INTERVAL_SLEEP = 1000L // 收到 CODEC_CONTROL_CALL_Sleep 后插入的单次暂停
// 收到 CODEC_CONTROL_CALL_Sleep 时置 true,下一次发包前会消费此标记并 sleep 一次更长的间隔
@Volatile
private var pendingCallSleep: Boolean = false
// 音频数据缓冲区,用于累积数据到80字节再发送 // 音频数据缓冲区,用于累积数据到80字节再发送
private val audioBuffer = mutableListOf<Byte>() private val audioBuffer = mutableListOf<Byte>()
// 重发机制相关常量 // 重发机制相关常量
@ -343,6 +347,14 @@ object BleService {
// 使用外部类的方法来分发唤醒信号,避免无限递归 // 使用外部类的方法来分发唤醒信号,避免无限递归
this@BleService.notifyWakeupSignalReceived() this@BleService.notifyWakeupSignalReceived()
} }
/**
* 通知通话休眠指令接收
*/
override fun notifyCallSleepReceived() {
// 使用外部类的方法来分发通话休眠指令,避免无限递归
this@BleService.notifyCallSleepReceived()
}
/** /**
* 通知开始 AI 单次对话 * 通知开始 AI 单次对话
@ -1339,7 +1351,7 @@ object BleService {
while (isAudioSending.get() && !Thread.currentThread().isInterrupted) { while (isAudioSending.get() && !Thread.currentThread().isInterrupted) {
try { try {
// 从队列中取出音频数据块 // 从队列中取出音频数据块
val audioChunk = audioSendQueue.poll(100, TimeUnit.MILLISECONDS) val audioChunk = audioSendQueue.poll(20, TimeUnit.MILLISECONDS)
if (audioChunk != null) { if (audioChunk != null) {
// 合包布局:[4B 序号(大端)] + [1B 声道(0=左/1=右)] + [N × opus] // 合包布局:[4B 序号(大端)] + [1B 声道(0=左/1=右)] + [N × opus]
@ -1362,7 +1374,13 @@ object BleService {
Log.i(TAG, "音频下行发送 ch=$chTag seq=$seq size=${audioChunk.size}B queue=${audioSendQueue.size}") Log.i(TAG, "音频下行发送 ch=$chTag seq=$seq size=${audioChunk.size}B queue=${audioSendQueue.size}")
} }
sendAudioChunkWithCallback(audioChunk) sendAudioChunkWithCallback(audioChunk)
Thread.sleep(AUDIO_SEND_INTERVAL) // 若刚收到通话休眠主动上报,则把下一次间隔替换为 SLEEP 间隔,
// 消费后立即清标记,确保只插入一次较长暂停
val intervalToUse = if (pendingCallSleep) {
pendingCallSleep = false
AUDIO_SEND_INTERVAL_SLEEP
} else AUDIO_SEND_INTERVAL_NORMAL
Thread.sleep(intervalToUse)
} }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
Log.d(TAG, "音频发送线程被中断") Log.d(TAG, "音频发送线程被中断")
@ -1919,6 +1937,16 @@ object BleService {
} }
} }
} }
/**
* 收到设备主动上报的 CODEC_CONTROL_CALL_Sleep 时调用。
* 仅设置一个一次性标记,由音频发送线程在下一次发包时消费并执行一次更长的暂停,
* 之后自动回到正常 60ms 节拍。多次连续触发会折叠为单次暂停(满足"已在休眠,无需叠加")。
*/
private fun notifyCallSleepReceived() {
pendingCallSleep = true
Log.i(TAG, "收到通话休眠指令,下一帧发送间隔将切到 ${AUDIO_SEND_INTERVAL_SLEEP}ms")
}
/** /**
* 向所有回调监听器分发开始 AI 单次对话 * 向所有回调监听器分发开始 AI 单次对话

2
local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift

@ -116,6 +116,8 @@ class BleConst {
static let CODEC_CONTROL_A2DP_PLAY: UInt8 = 0xA2 static let CODEC_CONTROL_A2DP_PLAY: UInt8 = 0xA2
/** mic和dac(音乐或者通话远端)声音 */ /** mic和dac(音乐或者通话远端)声音 */
static let CODEC_CONTROL_CALL_RECORD_PLAY: UInt8 = 0xA3 static let CODEC_CONTROL_CALL_RECORD_PLAY: UInt8 = 0xA3
/** 通话休眠 */
static let CODEC_CONTROL_CALL_Sleep: UInt8 = 0xFF
/** 打开编码指令 */ /** 打开编码指令 */
static let CODEC_CONTROL_ENCODE_ON: UInt8 = 0xB1 static let CODEC_CONTROL_ENCODE_ON: UInt8 = 0xB1
/** 左声道 */ /** 左声道 */

51
local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift

@ -115,7 +115,12 @@ public class BleService: NSObject {
// 音频发送配置常量 // 音频发送配置常量
private let AUDIO_CHUNK_SIZE = 120 // 每次发送120字节 private let AUDIO_CHUNK_SIZE = 120 // 每次发送120字节
private let AUDIO_SEND_INTERVAL: TimeInterval = 0.06 // 60毫秒间隔 private let AUDIO_SEND_INTERVAL: TimeInterval = 0.06 // 60毫秒间隔
// 收到 CODEC_CONTROL_CALL_Sleep 时插入的单次暂停时长(单位:秒)
private let AUDIO_SEND_SLEEP_PAUSE: TimeInterval = 1.0
// 在该时间点之前 processAudioSendQueue 不消费队列,实现"插入一次 1000ms 暂停"
private var nextSendEarliestAt: Date?
private let nextSendEarliestLock = NSLock()
private let audioQueueLock = NSLock() // 队列操作锁 private let audioQueueLock = NSLock() // 队列操作锁
//private var currentCommand: Command? //private var currentCommand: Command?
@ -1414,6 +1419,24 @@ public class BleService: NSObject {
"isNotification": true "isNotification": true
] ]
notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap) notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap)
case BleConst.CMD_CONTROL_CODEC:
// 设备主动上报编解码控制状态,目前只关心 CODEC_CONTROL_CALL_Sleep (0xFF)
// 帧格式: 0xCC 0x05 [len] [codecStatus, channelMode, ...] [crc]
let codecStatus: Int = notifyData.count >= 1 ? Int(notifyData[0]) : -1
if codecStatus == Int(BleConst.CODEC_CONTROL_CALL_Sleep) {
os_log("收到主动上报: 通话休眠 (codecStatus=0xFF)", log: logger, type: .info)
handleCallSleepReceived()
} else {
os_log("收到主动上报编解码控制: codecStatus=0x%02X", log: logger, type: .debug, codecStatus)
}
let resultMap: [String: Any] = [
"success": true,
"command": Int(infoType),
"rawData": [UInt8](notifyData),
"codecStatus": codecStatus,
"isNotification": true
]
notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap)
default: default:
// 对于未知上报类型,返回原始数据 // 对于未知上报类型,返回原始数据
let resultMap: [String: Any] = [ let resultMap: [String: Any] = [
@ -1592,19 +1615,39 @@ extension BleService: SwiftOpusAudioProcessor.AudioDataCallback {
* 处理音频发送队列 * 处理音频发送队列
*/ */
private func processAudioSendQueue() { private func processAudioSendQueue() {
// 若收到过 CODEC_CONTROL_CALL_Sleep,本轮直接放弃发送,等到 nextSendEarliestAt 之后再消费队列
nextSendEarliestLock.lock()
let earliest = nextSendEarliestAt
nextSendEarliestLock.unlock()
if let earliest = earliest, Date() < earliest {
return
}
audioQueueLock.lock() audioQueueLock.lock()
defer { audioQueueLock.unlock() } defer { audioQueueLock.unlock() }
guard !audioDataQueue.isEmpty else { return } guard !audioDataQueue.isEmpty else { return }
// 从队列中取出音频数据块 // 从队列中取出音频数据块
let audioChunk = audioDataQueue.removeFirst() let audioChunk = audioDataQueue.removeFirst()
// 在后台队列中发送音频数据块 // 在后台队列中发送音频数据块
audioSendQueue.async { [weak self] in audioSendQueue.async { [weak self] in
self?.sendAudioChunk(audioChunk) self?.sendAudioChunk(audioChunk)
} }
} }
/**
* 收到设备主动上报的 CODEC_CONTROL_CALL_Sleep 时调用,
* 让音频发送定时器在 AUDIO_SEND_SLEEP_PAUSE 内跳过发送,相当于插入一次较长暂停,
* 之后自动回到 60ms 节拍。期间重复触发会"刷新"暂停截止时间。
*/
private func handleCallSleepReceived() {
nextSendEarliestLock.lock()
nextSendEarliestAt = Date().addingTimeInterval(AUDIO_SEND_SLEEP_PAUSE)
nextSendEarliestLock.unlock()
os_log("收到通话休眠指令,音频发送将暂停 %.0f ms", log: logger, type: .info, AUDIO_SEND_SLEEP_PAUSE * 1000)
}
/** /**
* 发送音频数据块到蓝牙设备 * 发送音频数据块到蓝牙设备

498
pubspec.lock

File diff suppressed because it is too large
Loading…
Cancel
Save