> 经典蓝牙、BLE、MFI、Gatt这些怎么跟iOS连接,分别怎么实现
⏺ iOS 蓝牙技术完整指南
1. 核心概念和区别
⏺ | 技术 | 全称 | 特点 | iOS 支持 | 使用场景 |
|------|----------------------------|---------------|---------------------|-----------------|
| 经典蓝牙 | Bluetooth Classic (BR/EDR) | 高速、高功耗、连续传输 | ❌ App 层不支持(仅系统层) | 音频传输(A2DP)、文件传输 |
| BLE | Bluetooth Low Energy | 低功耗、短数据包、间歇传输 | ✅ CoreBluetooth | 健康设备、智能家居、传感器 |
| MFI | Made for iPhone/iPad/iPod | Apple 硬件认证计划 | ✅ ExternalAccessory | AirPods、认证配件 |
| GATT | Generic Attribute Profile | BLE 的数据协议层 | ✅ CoreBluetooth 内置 | BLE 通信的标准协议 |
层级关系:
经典蓝牙 BLE(低功耗蓝牙)
↓ ↓
iOS 不支持 App 开发 CoreBluetooth 框架
↓
GATT 协议
↓
Service → Characteristic → Descriptor
MFI(独立认证体系)
↓
ExternalAccessory 框架
↓
EA Protocol(自定义协议)
2. BLE(低功耗蓝牙)实现
⏺ 框架: CoreBluetooth
2.1 基本架构
import CoreBluetooth
class BLEManager: NSObject {
// 中心管理器(扫描和连接设备)
var centralManager: CBCentralManager!
// 外设管理器(作为蓝牙外设被扫描)
var peripheralManager: CBPeripheralManager!
// 已连接的外设
var connectedPeripheral: CBPeripheral?
override init() {
super.init()
// 创建中心管理器(默认在主队列)
centralManager = CBCentralManager(delegate: self, queue: nil)
}
}
2.2 完整实现流程
// MARK: - 中心设备(Central)- 扫描和连接外设
class BLEManager: NSObject {
// 定义你的服务和特征 UUID(由硬件厂商提供)
let serviceUUID = CBUUID(string: "0000FFF0-0000-1000-8000-00805F9B34FB")
let characteristicUUID = CBUUID(string: "0000FFF1-0000-1000-8000-00805F9B34FB")
var centralManager: CBCentralManager!
var discoveredPeripherals: [CBPeripheral] = []
var connectedPeripheral: CBPeripheral?
var targetCharacteristic: CBCharacteristic?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
// 开始扫描
func startScanning() {
guard centralManager.state == .poweredOn else {
print("蓝牙未开启")
return
}
// 扫描指定服务的设备(传 nil 扫描所有设备)
centralManager.scanForPeripherals(
withServices: [serviceUUID], // 只扫描包含该服务的设备
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
)
print("开始扫描 BLE 设备...")
}
// 停止扫描
func stopScanning() {
centralManager.stopScan()
print("停止扫描")
}
// 连接外设
func connect(peripheral: CBPeripheral) {
centralManager.connect(peripheral, options: nil)
}
// 断开连接
func disconnect() {
guard let peripheral = connectedPeripheral else { return }
centralManager.cancelPeripheralConnection(peripheral)
}
// 发送数据
func sendData(_ data: Data) {
guard
let peripheral = connectedPeripheral,
let characteristic = targetCharacteristic
else {
print("未连接或特征未找到")
return
}
// 写入数据(withResponse: 等待外设确认)
peripheral.writeValue(
data,
for: characteristic,
type: .withResponse // 或 .withoutResponse
)
}
}
// MARK: - CBCentralManagerDelegate
extension BLEManager: CBCentralManagerDelegate {
// 1️⃣ 蓝牙状态变化
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("✅ 蓝牙已开启")
startScanning()
case .poweredOff:
print("❌ 蓝牙已关闭")
case .unauthorized:
print("⚠️ 未授权蓝牙权限")
case .unsupported:
print("⚠️ 设备不支持蓝牙")
case .resetting:
print("🔄 蓝牙重置中")
case .unknown:
print("❓ 蓝牙状态未知")
@unknown default:
break
}
}
// 2️⃣ 发现外设
func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber
) {
print("发现设备: \(peripheral.name ?? "未命名"), RSSI: \(RSSI)")
// 保存外设(防止被释放)
if !discoveredPeripherals.contains(peripheral) {
discoveredPeripherals.append(peripheral)
}
// 自动连接符合条件的设备
if peripheral.name?.contains("Soundcore") == true {
connect(peripheral: peripheral)
stopScanning() // 找到目标设备后停止扫描
}
}
// 3️⃣ 连接成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("✅ 连接成功: \(peripheral.name ?? "未命名")")
connectedPeripheral = peripheral
peripheral.delegate = self
// 发现服务
peripheral.discoverServices([serviceUUID]) // 或传 nil 发现所有服务
}
// 4️⃣ 连接失败
func centralManager(
_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?
) {
print("❌ 连接失败: \(error?.localizedDescription ?? "")")
}
// 5️⃣ 断开连接
func centralManager(
_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: Error?
) {
print("🔌 设备断开: \(peripheral.name ?? "未命名")")
connectedPeripheral = nil
// 可选:自动重连
// centralManager.connect(peripheral, options: nil)
}
}
// MARK: - CBPeripheralDelegate
extension BLEManager: CBPeripheralDelegate {
// 6️⃣ 发现服务
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let error = error {
print("❌ 发现服务失败: \(error.localizedDescription)")
return
}
guard let services = peripheral.services else { return }
for service in services {
print("📦 发现服务: \(service.uuid)")
// 发现服务的特征
peripheral.discoverCharacteristics([characteristicUUID], for: service)
}
}
// 7️⃣ 发现特征
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?
) {
if let error = error {
print("❌ 发现特征失败: \(error.localizedDescription)")
return
}
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print("📝 发现特征: \(characteristic.uuid)")
print(" 属性: \(characteristic.properties)")
// 保存可写特征
if characteristic.uuid == characteristicUUID {
targetCharacteristic = characteristic
}
// 如果特征支持通知,订阅通知
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
print(" ✅ 已订阅通知")
}
// 如果特征支持读取,读取数据
if characteristic.properties.contains(.read) {
peripheral.readValue(for: characteristic)
}
}
}
// 8️⃣ 读取到数据(或收到通知)
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
if let error = error {
print("❌ 读取数据失败: \(error.localizedDescription)")
return
}
guard let data = characteristic.value else { return }
print("📥 收到数据: \(data.hexString)") // 需要自定义 hexString 扩展
// 解析数据
parseData(data)
}
// 9️⃣ 写入数据完成
func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?
) {
if let error = error {
print("❌ 写入失败: \(error.localizedDescription)")
} else {
print("✅ 写入成功")
}
}
// 🔟 通知状态变化
func peripheral(
_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?
) {
if let error = error {
print("❌ 订阅通知失败: \(error.localizedDescription)")
} else {
print("✅ 通知状态: \(characteristic.isNotifying)")
}
}
// 辅助方法
private func parseData(_ data: Data) {
// 根据协议解析数据
// 例如: 电池电量、设备状态等
}
}
// MARK: - Data 扩展
extension Data {
var hexString: String {
return map { String(format: "%02X", $0) }.joined(separator: " ")
}
}
2.3 GATT 协议结构
设备 (Peripheral)
└── 服务 (Service) - UUID: 0xFFF0
├── 特征 (Characteristic) - UUID: 0xFFF1
│ ├── 属性: Read, Write, Notify
│ ├── 值: Data
│ └── 描述符 (Descriptor)
│ └── Client Characteristic Configuration (0x2902)
│
└── 特征 (Characteristic) - UUID: 0xFFF2
└── 属性: Read, Indicate
常见属性:
// CBCharacteristicProperties
.read // 可读
.write // 可写(需要响应)
.writeWithoutResponse // 可写(不需要响应,速度快)
.notify // 通知(设备主动推送数据)
.indicate // 指示(类似通知,但需要确认)
.broadcast // 广播
3. 经典蓝牙(Classic Bluetooth)
⏺ 重要限制: ⚠️ iOS 不支持 App 层直接使用经典蓝牙
// ❌ iOS 不提供经典蓝牙 API
// 经典蓝牙只能通过系统层使用,例如:
// - A2DP 协议 (蓝牙音频)
// - HFP 协议 (蓝牙通话)
// - 系统设置中的蓝牙配对
替代方案:
-
使用 BLE 代替 - 大多数场景可以用 BLE 实现
-
使用 MFI + ExternalAccessory - 通过 MFI 认证后使用 iAP2 协议
-
SPP over BLE - 通过 BLE 模拟串口
经典蓝牙的系统级使用:
// 用户在系统设置中配对蓝牙音频设备后
// 你的 App 可以使用 AVFoundation 播放音频
import AVFoundation
let audioSession = AVAudioSession.sharedInstance()
try? audioSession.setCategory(.playback)
try? audioSession.setActive(true)
// 音频会自动路由到蓝牙设备
// 但你无法通过代码控制蓝牙连接本身
4. MFI(Made for iPhone)认证
⏺ 框架: ExternalAccessory
4.1 MFI 认证流程
- 加入 Apple MFi 计划
└── 申请网址: https://mfi.apple.com
└── 需要公司资质、NDA 签署
- 硬件设计
└── 集成 Apple 认证芯片(MFi Co-Processor)
└── 通过 Apple 硬件测试
- 获得认证
└── 获取 Protocol String(如 "com.company.product")
└── 在 Info.plist 中声明协议
- 开发 iOS App
└── 使用 ExternalAccessory 框架
4.2 配置 Info.plist
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>com.soundcore.headphone</string> <!-- 你的协议字符串 -->
</array>
<!-- iOS 13+ 必须添加蓝牙权限说明 -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要连接您的 Soundcore 设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要连接您的 Soundcore 设备</string>
4.3 完整实现
import ExternalAccessory
class MFIAccessoryManager: NSObject {
static let shared = MFIAccessoryManager()
// 配件管理器
let accessoryManager = EAAccessoryManager.shared()
// 当前连接的配件
var connectedAccessory: EAAccessory?
// 会话
var session: EASession?
// 输入输出流
var inputStream: InputStream?
var outputStream: OutputStream?
// 协议字符串(与硬件固件中定义的一致)
let protocolString = "com.soundcore.headphone"
override init() {
super.init()
setupNotifications()
checkConnectedAccessories()
}
// MARK: - 配置通知
private func setupNotifications() {
// 监听配件连接
NotificationCenter.default.addObserver(
self,
selector: #selector(accessoryConnected),
name: .EAAccessoryDidConnect,
object: nil
)
// 监听配件断开
NotificationCenter.default.addObserver(
self,
selector: #selector(accessoryDisconnected),
name: .EAAccessoryDidDisconnect,
object: nil
)
// 注册通知(必须调用)
accessoryManager.registerForLocalNotifications()
}
// MARK: - 检查已连接的配件
func checkConnectedAccessories() {
let accessories = accessoryManager.connectedAccessories
print("当前连接的配件数量: \(accessories.count)")
for accessory in accessories {
print("配件名称: \(accessory.name)")
print("制造商: \(accessory.manufacturer)")
print("型号: \(accessory.modelNumber)")
print("序列号: \(accessory.serialNumber)")
print("固件版本: \(accessory.firmwareRevision)")
print("硬件版本: \(accessory.hardwareRevision)")
print("支持的协议: \(accessory.protocolStrings)")
// 查找支持我们协议的配件
if accessory.protocolStrings.contains(protocolString) {
connectToAccessory(accessory)
}
}
}
// MARK: - 连接配件
func connectToAccessory(_ accessory: EAAccessory) {
// 创建会话
guard let session = EASession(accessory: accessory, forProtocol: protocolString) else {
print("❌ 创建会话失败")
return
}
self.session = session
self.connectedAccessory = accessory
// 获取输入输出流
guard
let inputStream = session.inputStream,
let outputStream = session.outputStream
else {
print("❌ 获取流失败")
return
}
self.inputStream = inputStream
self.outputStream = outputStream
// 设置代理
inputStream.delegate = self
outputStream.delegate = self
// 将流添加到 RunLoop
inputStream.schedule(in: .current, forMode: .default)
outputStream.schedule(in: .current, forMode: .default)
// 打开流
inputStream.open()
outputStream.open()
print("✅ 连接成功: \(accessory.name)")
}
// MARK: - 发送数据
func sendData(_ data: Data) {
guard let outputStream = outputStream, outputStream.hasSpaceAvailable else {
print("❌ 输出流不可用")
return
}
let bytes = [UInt8](data)
let bytesWritten = outputStream.write(bytes, maxLength: bytes.count)
if bytesWritten > 0 {
print("✅ 发送 \(bytesWritten) 字节")
} else {
print("❌ 发送失败")
}
}
// MARK: - 断开连接
func disconnect() {
inputStream?.close()
outputStream?.close()
inputStream?.remove(from: .current, forMode: .default)
outputStream?.remove(from: .current, forMode: .default)
inputStream = nil
outputStream = nil
session = nil
connectedAccessory = nil
print("🔌 已断开连接")
}
// MARK: - 通知处理
@objc private func accessoryConnected(notification: Notification) {
guard let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {
return
}
print("📱 配件已连接: \(accessory.name)")
if accessory.protocolStrings.contains(protocolString) {
connectToAccessory(accessory)
}
}
@objc private func accessoryDisconnected(notification: Notification) {
guard let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {
return
}
print("🔌 配件已断开: \(accessory.name)")
if accessory == connectedAccessory {
disconnect()
}
}
deinit {
accessoryManager.unregisterForLocalNotifications()
NotificationCenter.default.removeObserver(self)
}
}
// MARK: - StreamDelegate
extension MFIAccessoryManager: StreamDelegate {
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case .openCompleted:
print("✅ 流打开成功")
case .hasBytesAvailable:
// 有数据可读
guard let inputStream = aStream as? InputStream else { return }
readData(from: inputStream)
case .hasSpaceAvailable:
print("✅ 输出流可写")
case .errorOccurred:
print("❌ 流错误: \(aStream.streamError?.localizedDescription ?? "")")
case .endEncountered:
print("🔚 流结束")
default:
break
}
}
private func readData(from stream: InputStream) {
let bufferSize = 1024
var buffer = [UInt8](repeating: 0, count: bufferSize)
while stream.hasBytesAvailable {
let bytesRead = stream.read(&buffer, maxLength: bufferSize)
if bytesRead > 0 {
let data = Data(bytes: buffer, count: bytesRead)
print("📥 收到 \(bytesRead) 字节: \(data.hexString)")
// 处理数据
handleReceivedData(data)
}
}
}
private func handleReceivedData(_ data: Data) {
// 根据协议解析数据
// 例如: 命令响应、设备状态等
}
}
// MARK: - 使用示例
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 检查已连接的 MFI 配件
MFIAccessoryManager.shared.checkConnectedAccessories()
}
@IBAction func sendCommand(_ sender: UIButton) {
// 发送命令(例如:获取电池电量)
let command = Data([0x01, 0x02, 0x03])
MFIAccessoryManager.shared.sendData(command)
}
}
4.4 MFI vs BLE 对比
| 特性 | MFI (ExternalAccessory) | BLE (CoreBluetooth) |
|------|-------------------------|---------------------|
| 硬件要求 | 需要 Apple 认证芯片 | 无需认证 |
| 认证成本 | 高(芯片费用 + 认证费) | 无 |
| 开发周期 | 长(需要认证) | 短 |
| 数据传输 | 可靠、稳定 | 可能丢包 |
| 传输速度 | 较快(SPP 协议) | 较慢(GATT 限制) |
| 功耗 | 较高 | 低 |
| 使用场景 | AirPods、高端配件 | 健康设备、传感器 |
5. 完整对比和使用建议
⏺ 技术选型决策树
你的需求是什么?
├─ 音频传输(音乐、通话)
│ └─ ❌ 使用经典蓝牙(iOS 不支持 App 控制)
│ └─ 💡 用户在系统设置中配对,App 自动使用
│
├─ 传感器数据(心率、温度、步数)
│ └─ ✅ 使用 BLE (CoreBluetooth)
│ └─ 低功耗、适合持续监测
│
├─ 大量数据传输(文件、固件升级)
│ ├─ 方案1: BLE + 分包传输(慢但通用)
│ └─ 方案2: MFI + ExternalAccessory(快但需认证)
│
├─ 高端配件(品牌产品)
│ └─ ✅ 使用 MFI
│ └─ 稳定性高、用户体验好
│
└─ 快速原型、低成本产品
└─ ✅ 使用 BLE
└─ 无需认证、开发快
实际产品案例
| 产品类型 | 使用技术 | 原因 |
|--------------|------------|-----------------|
| AirPods | MFI | 高稳定性、音频 + 控制 |
| Apple Watch | BLE + 经典蓝牙 | BLE 传感器数据 + 音频 |
| 小米手环 | BLE | 低功耗、低成本 |
| Soundcore 耳机 | BLE + MFI | BLE 控制 + MFI 认证 |
| 血糖仪 | BLE | 医疗设备、低功耗 |
| 车载蓝牙 | 经典蓝牙 | 音频传输(用户系统配对) |
6. Soundcore 项目的实现方式
根据你的代码(SCWebscoketManage、SCTranlationRealTimeHandler),推测你们的架构:
// 你们的架构
┌─────────────────────────────────────┐
│ Soundcore App │
├─────────────────────────────────────┤
│ BLE (CoreBluetooth) │ ← 基础连接和控制
│ └─ 设备状态、电池电量、按键控制 │
├─────────────────────────────────────┤
│ WebSocket (SpeechKit) │ ← 实时翻译
│ └─ 音频流传输、AI 翻译 │
├─────────────────────────────────────┤
│ 可能的 MFI │ ← 如果有认证
│ └─ 高级功能、固件升级 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Soundcore Headphone │
│ (BLE Peripheral + WebSocket Client)│
└─────────────────────────────────────┘
推荐实现:
// 1. BLE 负责设备基础通信
class SoundcoreBLEManager {
// 连接、断开、电池电量、按键事件
func connect()
func getBatteryLevel() -> Int
func sendControlCommand(_ command: Data)
}
// 2. WebSocket 负责实时翻译数据
class SCWebscoketManage {
// 音频流、翻译文本
func sendAudioData(_ data: Data)
func receiveTranslation() -> String
}
// 3. 如果有 MFI 认证,用于固件升级
class MFIFirmwareUpdater {
func updateFirmware(file: URL)
}
7. 完整示例:BLE + WebSocket 混合架构
// 综合管理器
class SoundcoreDeviceManager {
static let shared = SoundcoreDeviceManager()
// BLE 管理器(设备控制)
let bleManager = BLEManager()
// WebSocket 管理器(翻译服务)
let websocketManager = SCWebscoketManage.shared
// 当前设备状态
var isConnected: Bool = false
var batteryLevel: Int = 0
// 初始化
func initialize() {
// 1. 启动 BLE 扫描
bleManager.startScanning()
// 2. BLE 连接成功后,初始化 WebSocket
bleManager.onConnected = { [weak self] in
self?.isConnected = true
self?.setupWebSocket()
}
// 3. 监听设备数据
bleManager.onDataReceived = { [weak self] data in
self?.handleDeviceData(data)
}
}
// 设置 WebSocket 连接
private func setupWebSocket() {
websocketManager.setupWebsocketConfig()
websocketManager.reconnectWebsocket(isReconnect: false)
}
// 处理设备数据
private func handleDeviceData(_ data: Data) {
// 解析 BLE 数据
if let battery = parseBatteryLevel(data) {
self.batteryLevel = battery
}
}
// 开始翻译
func startTranslation() {
guard isConnected else {
print("设备未连接")
return
}
// 通过 WebSocket 开始翻译会话
// ...
}
private func parseBatteryLevel(_ data: Data) -> Int? {
// 根据协议解析电池电量
guard data.count >= 2 else { return nil }
return Int(data[1])
}
}
8. 常见问题和注意事项
⏺ Q1: BLE 连接后立即断开?
// ❌ 错误:没有保持 peripheral 引用
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, ...) {
central.connect(peripheral, options: nil)
// peripheral 被释放,连接失败
}
// ✅ 正确:保持强引用
var discoveredPeripherals: [CBPeripheral] = []
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, ...) {
discoveredPeripherals.append(peripheral) // 保持引用
central.connect(peripheral, options: nil)
}
Q2: 写入数据失败?
// 检查特征属性
if characteristic.properties.contains(.write) {
// 使用 .withResponse
peripheral.writeValue(data, for: characteristic, type: .withResponse)
} else if characteristic.properties.contains(.writeWithoutResponse) {
// 使用 .withoutResponse
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
}
Q3: 收不到通知?
// 1. 确保特征支持通知
guard characteristic.properties.contains(.notify) else { return }
// 2. 订阅通知
peripheral.setNotifyValue(true, for: characteristic)
// 3. 实现代理方法
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// 处理数据
}
Q4: 后台保活?
// Info.plist 添加后台模式
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
// 创建 CentralManager 时指定恢复标识
let options = [CBCentralManagerOptionRestoreIdentifierKey: "com.soundcore.ble"]
centralManager = CBCentralManager(delegate: self, queue: nil, options: options)
Q5: MFI 配件找不到?
// 确保 Info.plist 正确配置
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>com.your.protocol</string> <!-- 与硬件固件一致 -->
</array>
// 确保注册通知
EAAccessoryManager.shared().registerForLocalNotifications()
总结
技术对比表
| 特性 | BLE | MFI | 经典蓝牙 |
|--------|---------------|-------------------|------------|
| iOS 框架 | CoreBluetooth | ExternalAccessory | 不支持 |
| 硬件成本 | 低 | 高 | 中 |
| 开发难度 | 中 | 高 | - |
| 传输速度 | 慢 (< 1 Mbps) | 快 | 快 (3 Mbps) |
| 功耗 | 低 | 中 | 高 |
| 使用场景 | 传感器、可穿戴 | 高端配件 | 音频(系统层) |
| 认证周期 | 无需 | 3-6 个月 | - |
推荐方案
你们的 Soundcore 项目应该使用:
- BLE (CoreBluetooth) - 基础设备连接和控制
-
扫描、连接、断开
-
电池电量、设备状态
-
按键事件
- WebSocket - 实时翻译功能
-
音频流传输
-
AI 翻译数据交互
- MFI (可选) - 如果需要高级功能
-
固件升级
-
更稳定的数据传输
-
品牌认证
需要我帮你分析 Soundcore 项目中 BLE 的具体实现吗