import Foundation import CoreBluetooth import os.log import SpeexKit // 使用杰理的SpeexKit库替代OpusSwift import AVFoundation @available(iOS 13.0, *) class BleService: NSObject { // MARK: - 单例实现 static let shared = BleService() // MARK: - 回调代理协议 protocol Callback: AnyObject { // 扫描相关回调 func onScanResult(devices: [[String: Any]]) // 连接状态相关回调 func onConnectionStateChanged(state: Int) // 数据相关回调 func onAudioDataReceived(data: Data) // 唤醒信号回调 func onWakeupSignalReceived() // 设备信息相关回调 func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) } // MARK: - 属性 private var centralManager: CBCentralManager? private var peripheralDevice: CBPeripheral? private var writeCharacteristic: CBCharacteristic? private var notifyCharacteristic: CBCharacteristic? private var audioCharacteristic: CBCharacteristic? private var isScanning = false private var callbacks = [Callback]() private var connectionState = BleConst.STATE_DISCONNECTED private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService") // MARK: - Opus音频处理器 private var opusProcessor: OpusAudioProcessor? // MARK: - 初始化 private override init() { super.init() #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { // iOS 18+下不立即初始化,等待AccessorySetupKit } else { initializeCentralManager() } #else initializeCentralManager() #endif // 初始化Opus音频处理器 initializeOpusProcessor() } // 延迟初始化中央管理器 private func initializeCentralManager() { if centralManager != nil { return } let options: [String: Any] = [ CBCentralManagerOptionRestoreIdentifierKey: "BLEAssistantCentral", CBCentralManagerOptionShowPowerAlertKey: true ] centralManager = CBCentralManager(delegate: self, queue: nil, options: options) } // 初始化Opus处理器 private func initializeOpusProcessor() { opusProcessor = OpusAudioProcessor() opusProcessor?.setCallback(self) // 立即启动Opus解码流,保持整个生命周期内活跃 startOpusDecoding() } // 启动Opus解码流 private func startOpusDecoding() { let opusStarted = opusProcessor?.startDecoding( sampleRate: 16000, // 设备端Opus编码采样率 channels: 1, // 单声道 frameSize: 640 // 40ms at 16kHz = 640 samples ) ?? false if opusStarted { os_log("Opus解码流启动成功,将在整个BLE生命周期内保持活跃", log: logger, type: .info) } else { os_log("Opus解码流启动失败", log: logger, type: .error) } } // MARK: - 回调管理 /// 添加回调代理 func addCallback(_ callback: Callback) -> Bool { if !callbacks.contains(where: { $0 === callback }) { callbacks.append(callback) os_log("已添加BLE回调, 当前回调数: %d", log: logger, type: .debug, callbacks.count) return true } return false } /// 移除回调代理 func removeCallback(_ callback: Callback) -> Bool { if let index = callbacks.firstIndex(where: { $0 === callback }) { callbacks.remove(at: index) os_log("已移除BLE回调, 当前回调数: %d", log: logger, type: .debug, callbacks.count) return true } return false } /// 清除所有回调 func clearCallbacks() { callbacks.removeAll() os_log("已清除所有BLE回调", log: logger, type: .debug) } // MARK: - 公共方法 /// 获取中央管理器,如果未初始化则先初始化 func getCentralManager() -> CBCentralManager { if centralManager == nil { initializeCentralManager() } return centralManager! } /// 开始扫描BLE设备 func startScan() { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { // iOS 18+不使用传统扫描 return } #endif if centralManager == nil { initializeCentralManager() } guard let manager = centralManager, manager.state == .poweredOn else { return } if isScanning { stopScan() } isScanning = true let serviceUUIDs = [BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID] manager.scanForPeripherals(withServices: serviceUUIDs, options: [ CBCentralManagerScanOptionAllowDuplicatesKey: false ]) os_log("开始扫描BLE设备...", log: logger, type: .info) } /// 停止扫描 func stopScan() { if isScanning && centralManager != nil { centralManager?.stopScan() isScanning = false os_log("停止BLE扫描", log: logger, type: .info) } } /// 连接到指定设备 func connect(to peripheral: CBPeripheral) { if connectionState == BleConst.STATE_CONNECTED || connectionState == BleConst.STATE_CONNECTING { return } if centralManager == nil { initializeCentralManager() } guard let manager = centralManager else { return } updateConnectionState(BleConst.STATE_CONNECTING) peripheralDevice = peripheral peripheralDevice?.delegate = self manager.connect(peripheral, options: [ CBConnectPeripheralOptionNotifyOnConnectionKey: true, CBConnectPeripheralOptionNotifyOnDisconnectionKey: true, CBConnectPeripheralOptionNotifyOnNotificationKey: true ]) os_log("开始连接到设备: %@", log: logger, type: .info, peripheral.identifier.uuidString) } /// 断开当前连接 func disconnect() { guard let peripheral = peripheralDevice, connectionState == BleConst.STATE_CONNECTED else { return } if centralManager != nil { updateConnectionState(BleConst.STATE_DISCONNECTING) centralManager?.cancelPeripheralConnection(peripheral) os_log("开始断开设备连接", log: logger, type: .info) } } /// 检查是否已连接 func isConnected() -> Bool { return connectionState == BleConst.STATE_CONNECTED } // MARK: - 回调通知方法 /// 通知所有回调扫描结果 private func notifyScanResult(devices: [[String: Any]]) { for callback in callbacks { callback.onScanResult(devices: devices) } } /// 通知所有回调连接状态变化 private func notifyConnectionStateChanged(state: Int) { for callback in callbacks { callback.onConnectionStateChanged(state: state) } } /// 通知所有回调接收到音频数据 private func notifyAudioDataReceived(data: Data) { for callback in callbacks { callback.onAudioDataReceived(data: data) } } /// 通知所有回调接收到唤醒信号 private func notifyWakeupSignalReceived() { for callback in callbacks { callback.onWakeupSignalReceived() } } /// 通知所有回调接收到设备信息 private func notifyDeviceInfoReceived(infoType: Int, infoData: [String: Any]) { for callback in callbacks { callback.onDeviceInfoReceived(infoType: infoType, infoData: infoData) } } // MARK: - 设备信息和控制命令 /// 获取设备版本号 func getDeviceVersion() -> Bool { os_log("获取耳机版本号...", log: logger, type: .info) let paramData = Data([0xFF]) return sendCommand(BleConst.CMD_GET_VERSION, data: paramData) } /// 获取产品ID func getProductId() -> Bool { os_log("获取耳机产品ID...", log: logger, type: .info) let paramData = Data([0xFF]) return sendCommand(BleConst.CMD_GET_PRODUCT_ID, data: paramData) } /// 获取颜色ID func getColorId() -> Bool { os_log("获取耳机颜色ID...", log: logger, type: .info) let paramData = Data([0xFF]) return sendCommand(BleConst.CMD_GET_COLOR_ID, data: paramData) } /// 获取电池信息 func getBatteryInfo() -> Bool { os_log("获取耳机电量信息...", log: logger, type: .info) let paramData = Data([0xFF]) return sendCommand(BleConst.CMD_GET_BATTERY_INFO, data: paramData) } // MARK: - 编解码控制 /// 打开编码器 func openEncoder() -> Bool { os_log("打开编码(设备侧)...", log: logger, type: .info) let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) } /// 打开解码器 func openDecoder() -> Bool { os_log("打开解码...", log: logger, type: .info) // 确保Opus解码器处于活跃状态(如果不是则启动) if opusProcessor?.isDecoding() != true { startOpusDecoding() } // 发送命令通知设备开始解码 let paramData = Data([BleConst.CODEC_CONTROL_DECODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) } /// 打开A2DP解码器 func openA2DPDecoder() -> Bool { os_log("打开A2DP解码...", log: logger, type: .info) let paramData = Data([BleConst.CODEC_CONTROL_A2DP_PLAY, BleConst.AUDIO_CHANNEL_RIGHT]) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) } /// 关闭编解码器 func closeCodec() -> Bool { os_log("关闭编解码...", log: logger, type: .info) // 不停止Opus解码流,只发送命令通知设备关闭编解码 let paramData = Data([BleConst.CODEC_CONTROL_CLOSE, BleConst.AUDIO_CHANNEL_RIGHT]) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) } /// 播放A2DP func playA2DP() -> Bool { os_log("切换至A2DP播放模式...", log: logger, type: .info) return sendCommand(BleConst.CMD_PLAY_A2DP) } /// 音量增大 func volumeUp() -> Bool { os_log("设备音量增加...", log: logger, type: .info) return sendCommand(BleConst.CMD_VOLUME_UP) } /// 音量减小 func volumeDown() -> Bool { os_log("设备音量减小...", log: logger, type: .info) return sendCommand(BleConst.CMD_VOLUME_DOWN) } /// 设置声道模式 func setChannelMode(_ channelMode: UInt8) -> Bool { let modeDesc: String switch channelMode { case BleConst.AUDIO_CHANNEL_LEFT: modeDesc = "左声道" case BleConst.AUDIO_CHANNEL_RIGHT: modeDesc = "右声道" case BleConst.AUDIO_CHANNEL_STEREO: modeDesc = "立体声" default: os_log("无效的声道模式: %d", log: logger, type: .error, channelMode) return false } os_log("设置%@模式...", log: logger, type: .info, modeDesc) let paramData = Data([0xA1, channelMode]) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) } // MARK: - 私有方法 /// 更新连接状态并通知回调 private func updateConnectionState(_ state: Int) { connectionState = state notifyConnectionStateChanged(state: state) } /// 向设备发送命令 private func sendCommand(_ cmd: UInt8, data: Data = Data()) -> Bool { guard let characteristic = writeCharacteristic, connectionState == BleConst.STATE_CONNECTED else { os_log("发送命令失败: 设备未连接", log: logger, type: .error) return false } do { // 命令数据长度 let cmdLength = UInt8(data.count) // 准备完整的命令包:帧头 + 命令ID + 长度 + 数据 + CRC var cmdPacket = Data() cmdPacket.append(BleConst.APP_REQUEST_HEADER) // 帧头 cmdPacket.append(cmd) // 命令类型 cmdPacket.append(cmdLength) // 命令长度 // 添加命令数据(如果有) if !data.isEmpty { cmdPacket.append(data) } // 计算CRC校验 - 对除CRC外的所有字节 let crc = calculateCRC8(data: cmdPacket) cmdPacket.append(crc) // 记录命令详情 var dataHexString = "" if !data.isEmpty { dataHexString = data.map { String(format: "0x%02X", $0) }.joined(separator: ", ") } os_log("发送命令: CMD=0x%02X, 长度=%d, 数据=[%@], CRC=0x%02X", log: logger, type: .debug, cmd, cmdLength, dataHexString, crc) // 发送数据 peripheralDevice?.writeValue(cmdPacket, for: characteristic, type: .withResponse) return true } catch { os_log("发送命令异常: %@", log: logger, type: .error, error.localizedDescription) return false } } /// 计算CRC8/MAXIM校验值 private func calculateCRC8(data: Data) -> UInt8 { var crc: UInt8 = 0 let polynomial: UInt8 = 0x31 for byte in data { crc ^= byte for _ in 0..<8 { if (crc & 0x80) != 0 { crc = ((crc << 1) ^ polynomial) } else { crc = (crc << 1) } } } return crc } /// 处理收到的音频数据 private func processAudioData(_ data: Data) { // 使用Opus处理器处理音频数据 opusProcessor?.processAudioData(data) } /// 处理接收到的响应数据 private func handleResponseData(_ data: Data) { guard data.count >= 4 else { os_log("响应数据格式错误:数据长度过短", log: logger, type: .error) return } // 提取帧信息 let header = data[0] let cmd = data[1] let length = data[2] // 检查帧头,确保是设备响应 guard header == BleConst.DEVICE_RESPONSE_HEADER else { // 如果是设备主动上报 if header == BleConst.DEVICE_NOTIFY_HEADER { handleNotificationData(data) } return } // 记录响应详情 var hexString = data.map { String(format: "0x%02X", $0) }.joined(separator: ", ") os_log("收到设备响应: %@", log: logger, type: .debug, hexString) // 检查数据长度 if data.count != Int(length) + 4 { // 帧头 + 命令 + 长度 + 数据 + CRC os_log("设备响应数据长度不匹配: 期望=%d, 实际=%d", log: logger, type: .error, Int(length) + 4, data.count) return } // 计算CRC校验 - 不包括最后一个字节(CRC) let payload = data.prefix(data.count - 1) let calculatedCrc = calculateCRC8(data: payload) let receivedCrc = data[data.count - 1] if calculatedCrc != receivedCrc { os_log("设备响应CRC校验失败: 计算=0x%02X, 接收=0x%02X", log: logger, type: .error, calculatedCrc, receivedCrc) return } // 提取响应数据 let responseData = length > 0 ? data.subdata(in: 3..<(3 + Int(length))) : Data() // 根据命令类型处理设备响应 switch cmd { case BleConst.CMD_GET_VERSION: var resultMap: [String: Any] = [ "success": true, "command": "versionInfo", "rawData": responseData ] if !responseData.isEmpty { if let versionStr = String(data: responseData, encoding: .utf8) { resultMap["version"] = versionStr os_log("接收到目标版本号: %@", log: logger, type: .info, versionStr) } else { // 如果无法解析为字符串,以点分隔的数字表示版本 let versionNums = responseData .map { $0 } .filter { $0 != 0xFF } // 过滤掉无效字节 .map { String($0) } .joined(separator: ".") resultMap["version"] = versionNums os_log("接收到目标版本号(数字格式): %@", log: logger, type: .info, versionNums) } } notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) case BleConst.CMD_GET_PRODUCT_ID: var resultMap: [String: Any] = [ "success": true, "command": "productId", "rawData": responseData ] if responseData.count >= 2 { let productId = UInt16(responseData[0]) << 8 | UInt16(responseData[1]) resultMap["productId"] = productId os_log("接收到产品ID: %d", log: logger, type: .info, productId) } else if !responseData.isEmpty { // 单字节产品ID let productId = UInt16(responseData[0]) resultMap["productId"] = productId os_log("接收到产品ID: %d", log: logger, type: .info, productId) } notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) case BleConst.CMD_GET_COLOR_ID: var resultMap: [String: Any] = [ "success": true, "command": "colorInfo", "rawData": responseData ] if !responseData.isEmpty { let colorId = Int(responseData[0]) resultMap["colorId"] = colorId os_log("接收到耳机颜色ID: %d", log: logger, type: .info, colorId) } notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) case BleConst.CMD_GET_BATTERY_INFO: var resultMap: [String: Any] = [ "success": true, "command": "batteryInfo", "rawData": responseData ] if responseData.count >= 3 { // 处理电池信息 - 左耳、右耳、充电仓 let leftEarBattery = Int(responseData[0] & 0x7F) let leftEarCharging = (responseData[0] & 0x80) != 0 let rightEarBattery = Int(responseData[1] & 0x7F) let rightEarCharging = (responseData[1] & 0x80) != 0 let caseBattery = Int(responseData[2] & 0x7F) let caseCharging = (responseData[2] & 0x80) != 0 // 添加到结果Map resultMap["leftEarBattery"] = leftEarBattery resultMap["leftEarCharging"] = leftEarCharging resultMap["rightEarBattery"] = rightEarBattery resultMap["rightEarCharging"] = rightEarCharging resultMap["caseBattery"] = caseBattery resultMap["caseCharging"] = caseCharging resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 os_log("接收到电量信息: 左耳=%d%% (%@), 右耳=%d%% (%@), 充电仓=%d%% (%@)", log: logger, type: .info, leftEarBattery, leftEarCharging ? "充电中" : "未充电", rightEarBattery, rightEarCharging ? "充电中" : "未充电", caseBattery, caseCharging ? "充电中" : "未充电") } else if !responseData.isEmpty { // 兼容单字节电量 let batteryLevel = Int(responseData[0]) resultMap["batteryLevel"] = batteryLevel os_log("接收到电量信息(简化): %d%%", log: logger, type: .info, batteryLevel) } notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) case BleConst.CMD_CONTROL_CODEC: var resultMap: [String: Any] = [ "success": true, "command": Int(cmd), "rawData": responseData ] if responseData.count >= 2 { let codecStatus = Int(responseData[0]) let channelMode = Int(responseData[1]) // 解码状态描述 let statusDesc: String switch codecStatus { case Int(BleConst.CODEC_CONTROL_CLOSE): statusDesc = "已关闭编解码" case Int(BleConst.CODEC_CONTROL_DECODE_ON): statusDesc = "已打开解码" case Int(BleConst.CODEC_CONTROL_A2DP_PLAY): statusDesc = "A2DP播放模式" case Int(BleConst.CODEC_CONTROL_ENCODE_ON): statusDesc = "已打开编码" default: statusDesc = "未知状态(\(codecStatus))" } // 声道模式描述 let channelDesc: String switch channelMode { case Int(BleConst.AUDIO_CHANNEL_LEFT): channelDesc = "左声道" case Int(BleConst.AUDIO_CHANNEL_RIGHT): channelDesc = "右声道" case Int(BleConst.AUDIO_CHANNEL_STEREO): channelDesc = "立体声" default: channelDesc = "未知声道(\(channelMode))" } resultMap["codecStatus"] = codecStatus resultMap["channelMode"] = channelMode resultMap["statusDesc"] = statusDesc resultMap["channelDesc"] = channelDesc os_log("编解码控制响应: %@, %@", log: logger, type: .info, statusDesc, channelDesc) } notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) default: // 对于未知命令,返回原始数据 let resultMap: [String: Any] = [ "success": true, "command": Int(cmd), "rawData": responseData, "isUnknown": true ] os_log("收到未知命令响应: 0x%02X", log: logger, type: .debug, cmd) notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) } } /// 处理设备主动上报的通知数据 private func handleNotificationData(_ data: Data) { guard data.count >= 4 else { os_log("设备主动上报数据格式错误:数据长度过短", log: logger, type: .error) return } let header = data[0] // 0xCC let infoType = data[1] let length = data[2] var hexString = data.map { String(format: "0x%02X", $0) }.joined(separator: ", ") os_log("收到设备主动上报: %@", log: logger, type: .debug, hexString) // 检查数据长度 if data.count != Int(length) + 4 { // 帧头 + 类型 + 长度 + 数据 + CRC os_log("设备主动上报数据长度不匹配: 期望=%d, 实际=%d", log: logger, type: .error, Int(length) + 4, data.count) return } // 计算CRC校验 - 不包括最后一个字节(CRC) let payload = data.prefix(data.count - 1) let calculatedCrc = calculateCRC8(data: payload) let receivedCrc = data[data.count - 1] if calculatedCrc != receivedCrc { os_log("设备主动上报CRC校验失败: 计算=0x%02X, 接收=0x%02X", log: logger, type: .error, calculatedCrc, receivedCrc) return } // 提取上报数据 let notifyData = length > 0 ? data.subdata(in: 3..<(3 + Int(length))) : Data() // 根据上报类型处理 switch infoType { case BleConst.CMD_GET_BATTERY_INFO: var resultMap: [String: Any] = [ "success": true, "command": Int(infoType), "rawData": notifyData, "isNotification": true // 标记为主动上报 ] if notifyData.count >= 3 { // 处理电池信息 - 左耳、右耳、充电仓 let leftEarBattery = Int(notifyData[0] & 0x7F) let leftEarCharging = (notifyData[0] & 0x80) != 0 let rightEarBattery = Int(notifyData[1] & 0x7F) let rightEarCharging = (notifyData[1] & 0x80) != 0 let caseBattery = Int(notifyData[2] & 0x7F) let caseCharging = (notifyData[2] & 0x80) != 0 // 添加到结果Map resultMap["leftEarBattery"] = leftEarBattery resultMap["leftEarCharging"] = leftEarCharging resultMap["rightEarBattery"] = rightEarBattery resultMap["rightEarCharging"] = rightEarCharging resultMap["caseBattery"] = caseBattery resultMap["caseCharging"] = caseCharging resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 os_log("收到主动上报电量信息: 左耳=%d%% (%@), 右耳=%d%% (%@), 充电仓=%d%% (%@)", log: logger, type: .info, leftEarBattery, leftEarCharging ? "充电中" : "未充电", rightEarBattery, rightEarCharging ? "充电中" : "未充电", caseBattery, caseCharging ? "充电中" : "未充电") } notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap) case BleConst.CMD_WAKE_UP: os_log("收到唤醒指令", log: logger, type: .info) // 接收到唤醒信号,打开编码器 (设备侧) openEncoder() notifyWakeupSignalReceived() default: // 对于未知上报类型,返回原始数据 let resultMap: [String: Any] = [ "success": true, "command": Int(infoType), "rawData": notifyData, "isUnknown": true, "isNotification": true // 标记为主动上报 ] os_log("收到未知主动上报类型: 0x%02X", log: logger, type: .debug, infoType) notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap) } } // MARK: - 资源清理 /// 清理所有资源 func cleanup() { stopScan() disconnect() // 停止Opus解码 opusProcessor?.cleanup() opusProcessor = nil os_log("BleService资源已清理", log: logger, type: .debug) } // MARK: - AccessorySetupKit /// 在接收到AccessorySetupKit的accessoryAdded事件后调用 func connectToAccessory(uuid: UUID) { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { // 确保CBCentralManager已初始化 if centralManager == nil { initializeCentralManager() } guard let manager = centralManager, manager.state == .poweredOn else { return } // 使用retrievePeripherals而不是扫描 let peripherals = manager.retrievePeripherals(withIdentifiers: [uuid]) if let peripheral = peripherals.first { connect(to: peripheral) } } #endif } } // MARK: - OpusAudioProcessor.AudioDataCallback @available(iOS 13.0, *) extension BleService: OpusAudioProcessor.AudioDataCallback { /// 接收到解码后的PCM音频数据 func onAudioDataReceived(data: Data) { notifyAudioDataReceived(data: data) } } // MARK: - CBCentralManagerDelegate @available(iOS 13.0, *) extension BleService: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .poweredOn: // 蓝牙已开启,可以开始扫描 break case .poweredOff: updateConnectionState(BleConst.STATE_DISCONNECTED) case .resetting, .unauthorized, .unsupported, .unknown: // 其他状态不处理 break @unknown default: break } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { // 将扫描结果转换为字典格式 var deviceInfo: [String: Any] = [ "name": peripheral.name ?? "Unknown", "id": peripheral.identifier.uuidString, "rssi": RSSI.intValue ] // 添加广播数据 if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { deviceInfo["manufacturerData"] = manufacturerData } if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { deviceInfo["serviceUUIDs"] = serviceUUIDs.map { $0.uuidString } } // 报告给回调 notifyScanResult(devices: [deviceInfo]) } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { updateConnectionState(BleConst.STATE_CONNECTED) // 查找服务 peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) // 确保Opus解码器处于活跃状态 if opusProcessor?.isDecoding() != true { startOpusDecoding() } } func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { updateConnectionState(BleConst.STATE_ERROR) } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { updateConnectionState(BleConst.STATE_DISCONNECTED) } func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { // 从恢复状态中获取之前连接的外设 if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral], let peripheral = peripherals.first { peripheralDevice = peripheral peripheralDevice?.delegate = self // 如果之前已连接,更新状态 if peripheral.state == .connected { updateConnectionState(BleConst.STATE_CONNECTED) // 重新发现服务 peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) } else { // 如果之前未连接,尝试重新连接 central.connect(peripheral, options: nil) } } } } // MARK: - CBPeripheralDelegate @available(iOS 13.0, *) extension BleService: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let error = error { return } guard let services = peripheral.services else { return } for service in services { if service.uuid == BleConst.PRIMARY_SERVICE_UUID { // 主服务,发现写入和通知特征 peripheral.discoverCharacteristics([BleConst.WRITE_CHAR_UUID, BleConst.NOTIFY_CHAR_UUID], for: service) } else if service.uuid == BleConst.AUDIO_SERVICE_UUID { // 音频服务,发现音频特征 peripheral.discoverCharacteristics([BleConst.RECEIVE_AUDIO_CHAR_UUID], for: service) } } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let error = error { return } guard let characteristics = service.characteristics else { return } for characteristic in characteristics { if characteristic.uuid == BleConst.WRITE_CHAR_UUID { writeCharacteristic = characteristic } else if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID { notifyCharacteristic = characteristic // 订阅通知 peripheral.setNotifyValue(true, for: characteristic) } else if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID { audioCharacteristic = characteristic // 订阅音频通知 peripheral.setNotifyValue(true, for: characteristic) } } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { return } guard let data = characteristic.value, !data.isEmpty else { return } if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID { // 处理通知数据 handleResponseData(data) } else if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID { // 处理音频数据 processAudioData(data) } } func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { // 写入完成回调,不需要特殊处理 } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { // 通知状态更新回调,不需要特殊处理 } }