|
|
@ -12,6 +12,7 @@ import java.util.concurrent.CopyOnWriteArrayList |
|
|
// Import Jieli Opus SDK classes |
|
|
// Import Jieli Opus SDK classes |
|
|
import com.jieli.jl_audio_decode.opus.model.OpusOption |
|
|
import com.jieli.jl_audio_decode.opus.model.OpusOption |
|
|
import com.jieli.jl_audio_decode.callback.OnDecodeStreamCallback |
|
|
import com.jieli.jl_audio_decode.callback.OnDecodeStreamCallback |
|
|
|
|
|
import com.jieli.jl_audio_decode.callback.OnEncodeStreamCallback |
|
|
import com.jieli.jl_audio_decode.opus.OpusManager |
|
|
import com.jieli.jl_audio_decode.opus.OpusManager |
|
|
import com.jieli.jl_audio_decode.exceptions.OpusException |
|
|
import com.jieli.jl_audio_decode.exceptions.OpusException |
|
|
|
|
|
|
|
|
@ -25,6 +26,7 @@ import android.os.Handler |
|
|
import java.util.concurrent.atomic.AtomicBoolean |
|
|
import java.util.concurrent.atomic.AtomicBoolean |
|
|
import android.util.Log |
|
|
import android.util.Log |
|
|
import androidx.annotation.RequiresPermission |
|
|
import androidx.annotation.RequiresPermission |
|
|
|
|
|
import java.util.concurrent.TimeUnit |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 |
|
|
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 |
|
|
@ -52,6 +54,10 @@ object BleService { |
|
|
|
|
|
|
|
|
// 数据相关回调 |
|
|
// 数据相关回调 |
|
|
fun onAudioDataReceived(data: ByteArray) |
|
|
fun onAudioDataReceived(data: ByteArray) |
|
|
|
|
|
// 数据相关回调 |
|
|
|
|
|
fun onAudioDataReceived1(data: ByteArray) |
|
|
|
|
|
|
|
|
|
|
|
// 唤醒信号相关回调 |
|
|
fun onWakeupSignalReceived() |
|
|
fun onWakeupSignalReceived() |
|
|
|
|
|
|
|
|
// 设备信息相关回调 - 统一回调接口 |
|
|
// 设备信息相关回调 - 统一回调接口 |
|
|
@ -76,8 +82,9 @@ object BleService { |
|
|
private var notifyChar: BluetoothGattCharacteristic? = null |
|
|
private var notifyChar: BluetoothGattCharacteristic? = null |
|
|
private var writeChar: BluetoothGattCharacteristic? = null |
|
|
private var writeChar: BluetoothGattCharacteristic? = null |
|
|
private var audioChar: BluetoothGattCharacteristic? = null |
|
|
private var audioChar: BluetoothGattCharacteristic? = null |
|
|
private var audioChar1: BluetoothGattCharacteristic? = null |
|
|
private var callWriteChar: BluetoothGattCharacteristic? = null |
|
|
var recordfile: RecordingFile? = null |
|
|
var recordfile: RecordingFile? = null |
|
|
|
|
|
var recordfile1: RecordingFile? = null |
|
|
|
|
|
|
|
|
// 扫描相关 |
|
|
// 扫描相关 |
|
|
private lateinit var scanHandler: Handler |
|
|
private lateinit var scanHandler: Handler |
|
|
@ -104,11 +111,58 @@ object BleService { |
|
|
|
|
|
|
|
|
// Opus解码器实例 |
|
|
// Opus解码器实例 |
|
|
private var opusManager: OpusManager? = null |
|
|
private var opusManager: OpusManager? = null |
|
|
|
|
|
private var option: OpusOption? = null |
|
|
private val mainHandler = Handler(Looper.getMainLooper()) |
|
|
private val mainHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
|
|
|
|
// 解码音频数据队列和处理线程 |
|
|
|
|
|
private val audioDataQueue = LinkedBlockingQueue<ByteArray>() |
|
|
|
|
|
private var audioQueueProcessorThread: Thread? = null |
|
|
|
|
|
|
|
|
|
|
|
// 音频数据缓存 |
|
|
|
|
|
private val audioDataBuffer = mutableListOf<Byte>() |
|
|
|
|
|
//private val AUDIO_BUFFER_SIZE = 1280// 1280字节缓存阈值 |
|
|
|
|
|
|
|
|
|
|
|
// 音频数据发送相关 |
|
|
|
|
|
private val audioSendQueue = LinkedBlockingQueue<ByteArray>() |
|
|
|
|
|
private var audioSendThread: Thread? = null |
|
|
|
|
|
private val audioSendHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
private val isAudioSending = AtomicBoolean(false) |
|
|
|
|
|
|
|
|
|
|
|
// 音频数据分块发送的常量 |
|
|
|
|
|
private val AUDIO_CHUNK_SIZE = 120 // 每次发送80字节 |
|
|
|
|
|
private val AUDIO_SEND_INTERVAL = 60L // 发送间隔20ms |
|
|
|
|
|
|
|
|
|
|
|
// 音频数据缓冲区,用于累积数据到80字节再发送 |
|
|
|
|
|
private val audioBuffer = mutableListOf<Byte>() |
|
|
|
|
|
|
|
|
|
|
|
// 重发机制相关常量 |
|
|
|
|
|
private val MAX_RETRY_COUNT = 1 // 最大重试次数 |
|
|
|
|
|
private val RETRY_DELAY = 30L // 重试延迟时间(毫秒) |
|
|
|
|
|
|
|
|
// 初始化状态 |
|
|
// 初始化状态 |
|
|
private var isInitialized = false |
|
|
private var isInitialized = false |
|
|
|
|
|
|
|
|
|
|
|
// 音频数据统计相关变量 |
|
|
|
|
|
private var bytesReceivedInCurrentSecond = 0 |
|
|
|
|
|
// 添加发送数据统计变量 |
|
|
|
|
|
private var bytesSentInCurrentSecond = 0 |
|
|
|
|
|
private var lastStatisticsTime = System.currentTimeMillis() |
|
|
|
|
|
private val statisticsHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
private val statisticsRunnable = object : Runnable { |
|
|
|
|
|
override fun run() { |
|
|
|
|
|
val currentTime = System.currentTimeMillis() |
|
|
|
|
|
val timeDiff = currentTime - lastStatisticsTime |
|
|
|
|
|
if (timeDiff >= 1000) { // 每秒统计一次 |
|
|
|
|
|
Log.i(TAG, "每秒接收音频数据: $bytesReceivedInCurrentSecond 字节") |
|
|
|
|
|
Log.i(TAG, "每秒发送音频数据: $bytesSentInCurrentSecond 字节") |
|
|
|
|
|
bytesReceivedInCurrentSecond = 0 |
|
|
|
|
|
bytesSentInCurrentSecond = 0 |
|
|
|
|
|
lastStatisticsTime = currentTime |
|
|
|
|
|
} |
|
|
|
|
|
statisticsHandler.postDelayed(this, 1000) // 每秒执行一次 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private val replyTimeoutHandler = Handler(Looper.getMainLooper()) |
|
|
private val replyTimeoutHandler = Handler(Looper.getMainLooper()) |
|
|
private val replyTimeoutRunnable = Runnable { |
|
|
private val replyTimeoutRunnable = Runnable { |
|
|
if (!isReply) { |
|
|
if (!isReply) { |
|
|
@ -128,20 +182,6 @@ object BleService { |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
this.context = appContext.applicationContext |
|
|
this.context = appContext.applicationContext |
|
|
|
|
|
|
|
|
// otaManager = OTAManager(this.context).apply { |
|
|
|
|
|
// // 设置数据回调 |
|
|
|
|
|
// setDataCallback(object : OTAManager.DataCallback { |
|
|
|
|
|
// override fun onDataReceived(device: BluetoothDevice?, data: ByteArray?) { |
|
|
|
|
|
// data?.let { |
|
|
|
|
|
// Log.i("BleService", "收到数据:${it.size} 字节") |
|
|
|
|
|
// Log.i("BleService", "认证交互数据${it.contentToString()}") |
|
|
|
|
|
// // 移除次数限制,除非明确需要 |
|
|
|
|
|
// otaManager?.onReceiveDeviceData(device, it) |
|
|
|
|
|
// } |
|
|
|
|
|
// } |
|
|
|
|
|
// }) |
|
|
|
|
|
// } |
|
|
|
|
|
// 初始化蓝牙管理器和适配器 |
|
|
// 初始化蓝牙管理器和适配器 |
|
|
bluetoothManager = |
|
|
bluetoothManager = |
|
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
@ -151,16 +191,23 @@ object BleService { |
|
|
// 初始化Handler |
|
|
// 初始化Handler |
|
|
scanHandler = Handler(Looper.getMainLooper()) |
|
|
scanHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
|
// 初始化OpusManager和OTAManager |
|
|
// 启动队列处理 |
|
|
|
|
|
startAudioQueueProcessing() |
|
|
|
|
|
|
|
|
|
|
|
// 初始化OpusManager和OpusOption |
|
|
try { |
|
|
try { |
|
|
opusManager = OpusManager() |
|
|
opusManager = OpusManager() |
|
|
startOpusStreamDecoding() |
|
|
option = OpusOption() |
|
|
Log.d(TAG, "OpusManager初始化成功") |
|
|
Log.d(TAG, "OpusManager初始化成功") |
|
|
} catch (e: OpusException) { |
|
|
} catch (e: OpusException) { |
|
|
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e) |
|
|
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e) |
|
|
// 根据需要决定是否因为Opus初始化失败而返回false |
|
|
// 根据需要决定是否因为Opus初始化失败而返回false |
|
|
} |
|
|
} |
|
|
recordfile = RecordingFile(this.context) |
|
|
recordfile = RecordingFile(this.context) |
|
|
|
|
|
recordfile!!.fileName = "不拆分" |
|
|
|
|
|
|
|
|
|
|
|
recordfile1 = RecordingFile(this.context) |
|
|
|
|
|
recordfile1!!.fileName = "重新压缩" |
|
|
isInitialized = true |
|
|
isInitialized = true |
|
|
Log.d(TAG, "BLE服务初始化成功") |
|
|
Log.d(TAG, "BLE服务初始化成功") |
|
|
return true |
|
|
return true |
|
|
@ -205,6 +252,25 @@ object BleService { |
|
|
Log.d(TAG, "已清除所有BLE回调") |
|
|
Log.d(TAG, "已清除所有BLE回调") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun writeExternalAudioData(data: ByteArray) { |
|
|
|
|
|
// 检查OpusManager是否已初始化 |
|
|
|
|
|
if (opusManager == null) { |
|
|
|
|
|
Log.e(TAG, "opusManager 未初始化") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果已经在编码流中,直接写入数据 |
|
|
|
|
|
if (opusManager?.isEncodeStream == true) { |
|
|
|
|
|
Log.d(TAG, "正在进行Opus编码流,写入音频数据") |
|
|
|
|
|
// 将外部音频数据写入编码流,每次处理1280字节 |
|
|
|
|
|
opusManager?.writeEncodeStream(data) |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "Opus编码流未启动,无法写入音频数据") |
|
|
|
|
|
// 可选:自动启动编码流 |
|
|
|
|
|
// startOpusEncodeStream() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
// 扫描功能 |
|
|
// 扫描功能 |
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
@ -360,6 +426,7 @@ object BleService { |
|
|
} |
|
|
} |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fun getDeviceInfos(deviceName: String): List<Map<String, Any>> { |
|
|
fun getDeviceInfos(deviceName: String): List<Map<String, Any>> { |
|
|
// 更新缓存 |
|
|
// 更新缓存 |
|
|
val matchedResults = scanResults.filter { |
|
|
val matchedResults = scanResults.filter { |
|
|
@ -389,6 +456,7 @@ object BleService { |
|
|
|
|
|
|
|
|
return ret |
|
|
return ret |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 打印设备信息和服务UUID |
|
|
* 打印设备信息和服务UUID |
|
|
*/ |
|
|
*/ |
|
|
@ -517,7 +585,7 @@ object BleService { |
|
|
notifyChar = null |
|
|
notifyChar = null |
|
|
writeChar = null |
|
|
writeChar = null |
|
|
audioChar = null |
|
|
audioChar = null |
|
|
audioChar1 = null |
|
|
callWriteChar = null |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -530,42 +598,56 @@ object BleService { |
|
|
notifyConnectionStateChanged(state) |
|
|
notifyConnectionStateChanged(state) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 在连接成功时启动统计(可以在onConnectionStateChange的连接成功分支中添加) |
|
|
|
|
|
private fun startBytesStatistics() { |
|
|
|
|
|
bytesReceivedInCurrentSecond = 0 |
|
|
|
|
|
lastStatisticsTime = System.currentTimeMillis() |
|
|
|
|
|
statisticsHandler.post(statisticsRunnable) |
|
|
|
|
|
Log.i(TAG, "开始统计每秒接收字节数") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 在断开连接时停止统计 |
|
|
|
|
|
private fun stopBytesStatistics() { |
|
|
|
|
|
statisticsHandler.removeCallbacks(statisticsRunnable) |
|
|
|
|
|
Log.i(TAG, "停止统计每秒接收字节数") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* GATT回调 |
|
|
* GATT回调 |
|
|
*/ |
|
|
*/ |
|
|
private val gattCallback = object : BluetoothGattCallback() { |
|
|
private val gattCallback = object : BluetoothGattCallback() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) { |
|
|
|
|
|
// if (status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
|
|
// Log.i(TAG, "MTU 更新成功: $mtu") |
|
|
|
|
|
// configureOTA() |
|
|
|
|
|
|
|
|
|
|
|
// startOTA() |
|
|
|
|
|
// } else { |
|
|
|
|
|
// Log.e(TAG, "MTU 更新失败: status=$status") |
|
|
|
|
|
// } |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) { |
|
|
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) { |
|
|
when { |
|
|
when { |
|
|
status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED -> { |
|
|
status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED -> { |
|
|
updateConnectionState(BleConst.STATE_CONNECTED) |
|
|
updateConnectionState(BleConst.STATE_CONNECTED) |
|
|
g.discoverServices() |
|
|
|
|
|
// Log.i(TAG, "连接成功,开始 MTU 协商") |
|
|
|
|
|
|
|
|
|
|
|
// otaManager?.onBtDeviceConnection(g.device, StateCode.CONNECTION_OK) |
|
|
// 设置PHY值为2M以提高传输速度 |
|
|
// g.requestMtu(512) // 触发 MTU 修改流程 |
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 首先尝试读取当前PHY状态 |
|
|
|
|
|
g.readPhy() |
|
|
|
|
|
|
|
|
|
|
|
// 然后请求设置2M PHY |
|
|
|
|
|
g.setPreferredPhy( |
|
|
|
|
|
BluetoothDevice.PHY_LE_2M_MASK, // TX PHY: 2M |
|
|
|
|
|
BluetoothDevice.PHY_LE_2M_MASK, // RX PHY: 2M |
|
|
|
|
|
BluetoothDevice.PHY_OPTION_NO_PREFERRED |
|
|
|
|
|
) |
|
|
|
|
|
Log.d(TAG, "已请求设置PHY为2M") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.w(TAG, "设置PHY失败: ${e.message},将使用默认1M PHY") |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "当前Android版本不支持PHY设置(需要API 26+),使用默认1M PHY") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
g.discoverServices() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
newState == BluetoothProfile.STATE_DISCONNECTED -> { |
|
|
newState == BluetoothProfile.STATE_DISCONNECTED -> { |
|
|
updateConnectionState(BleConst.STATE_DISCONNECTED) |
|
|
updateConnectionState(BleConst.STATE_DISCONNECTED) |
|
|
disconnectGatt() |
|
|
disconnectGatt() |
|
|
|
|
|
|
|
|
// otaManager?.onBtDeviceConnection(g.device, StateCode.CONNECTION_CONNECTING) |
|
|
|
|
|
// g.close() |
|
|
|
|
|
// otaManager?.release(); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
else -> { |
|
|
else -> { |
|
|
@ -575,6 +657,45 @@ object BleService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* PHY读取回调 - 获取当前PHY状态 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun onPhyRead(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) { |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
|
|
val txPhyStr = getPhyString(txPhy) |
|
|
|
|
|
val rxPhyStr = getPhyString(rxPhy) |
|
|
|
|
|
Log.i(TAG, "当前PHY状态: TX=$txPhyStr, RX=$rxPhyStr") |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "读取PHY状态失败: status=$status") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* PHY更新回调 - 监控PHY设置结果 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun onPhyUpdate(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) { |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
|
|
when (status) { |
|
|
|
|
|
BluetoothGatt.GATT_SUCCESS -> { |
|
|
|
|
|
val txPhyStr = getPhyString(txPhy) |
|
|
|
|
|
val rxPhyStr = getPhyString(rxPhy) |
|
|
|
|
|
Log.i(TAG, "PHY更新成功: TX=$txPhyStr, RX=$rxPhyStr") |
|
|
|
|
|
} |
|
|
|
|
|
6 -> { // GATT_REQUEST_NOT_SUPPORTED |
|
|
|
|
|
Log.w(TAG, "设备不支持PHY更新,将继续使用1M PHY进行通信") |
|
|
|
|
|
} |
|
|
|
|
|
133 -> { // GATT_ERROR |
|
|
|
|
|
Log.w(TAG, "PHY更新出现一般性错误,可能是设备兼容性问题") |
|
|
|
|
|
} |
|
|
|
|
|
else -> { |
|
|
|
|
|
Log.w(TAG, "PHY更新失败: status=$status") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
override fun onServicesDiscovered(g: BluetoothGatt, status: Int) { |
|
|
override fun onServicesDiscovered(g: BluetoothGatt, status: Int) { |
|
|
if (status != BluetoothGatt.GATT_SUCCESS) { |
|
|
if (status != BluetoothGatt.GATT_SUCCESS) { |
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
@ -592,16 +713,18 @@ object BleService { |
|
|
audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID) |
|
|
audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取音频服务1特征 |
|
|
// 获取通话音频服务特征 |
|
|
val audioSvc1 = g.getService(BleConst.AUDIO_SERVICE_UUID1) |
|
|
val callAudioSvc = g.getService(BleConst.CALL_AUDIO_SERVICE_UUID) |
|
|
audioChar1 = audioSvc1?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID1) |
|
|
callWriteChar = callAudioSvc?.getCharacteristic(BleConst.CALL_WRITE_AUDIO_CHAR_UUID) |
|
|
if (notifyChar == null || writeChar == null) { |
|
|
if (callWriteChar == null) { |
|
|
Log.e(TAG, "未找到主服务所需特征") |
|
|
Log.e(TAG, "未找到通话音频主服务所需特征") |
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
Log.d(TAG, "找到通话音频服务所需特征") |
|
|
|
|
|
startBytesStatistics() |
|
|
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE |
|
|
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE |
|
|
|
|
|
callWriteChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE |
|
|
//要先设置音频服务的通知,否则接收不到 |
|
|
//要先设置音频服务的通知,否则接收不到 |
|
|
// 设置音频服务的通知(如果存在) |
|
|
// 设置音频服务的通知(如果存在) |
|
|
if (audioChar != null) { |
|
|
if (audioChar != null) { |
|
|
@ -610,15 +733,9 @@ object BleService { |
|
|
} else { |
|
|
} else { |
|
|
Log.w(TAG, "音频服务特征未找到") |
|
|
Log.w(TAG, "音频服务特征未找到") |
|
|
} |
|
|
} |
|
|
if (audioChar1 != null) { |
|
|
|
|
|
setupNotifications(g, audioChar1) |
|
|
|
|
|
Log.i(TAG, "音频服务特征找到并设置通知") |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "音频服务特征未找到") |
|
|
|
|
|
} |
|
|
|
|
|
// 设置主服务的通知 |
|
|
// 设置主服务的通知 |
|
|
setupNotifications(g, notifyChar) |
|
|
setupNotifications(g, notifyChar) |
|
|
//开启解码 |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -628,15 +745,11 @@ object BleService { |
|
|
|
|
|
|
|
|
// 根据特征UUID区分处理 |
|
|
// 根据特征UUID区分处理 |
|
|
when (c.uuid) { |
|
|
when (c.uuid) { |
|
|
// 音频特征数据 |
|
|
|
|
|
// BleConst.RECEIVE_AUDIO_CHAR_UUID1 -> { |
|
|
|
|
|
|
|
|
|
|
|
// Log.i(TAG, "RECEIVE_AUDIO_CHAR_UUID1") |
|
|
|
|
|
// processAudioData(data) |
|
|
|
|
|
// } |
|
|
|
|
|
// 音频特征数据 |
|
|
// 音频特征数据 |
|
|
BleConst.RECEIVE_AUDIO_CHAR_UUID -> { |
|
|
BleConst.RECEIVE_AUDIO_CHAR_UUID -> { |
|
|
|
|
|
|
|
|
|
|
|
// 统计接收到的字节数 |
|
|
|
|
|
bytesReceivedInCurrentSecond += data.size |
|
|
processAudioData(data) |
|
|
processAudioData(data) |
|
|
} |
|
|
} |
|
|
// 通知特征数据(命令和控制) |
|
|
// 通知特征数据(命令和控制) |
|
|
@ -713,12 +826,30 @@ object BleService { |
|
|
*/ |
|
|
*/ |
|
|
private fun processAudioData(data: ByteArray) { |
|
|
private fun processAudioData(data: ByteArray) { |
|
|
try { |
|
|
try { |
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
recordfile?.saveAudioDataToWav(data) |
|
|
recordfile?.saveAudioDataToWav(data) |
|
|
opusManager?.writeAudioStream(data) |
|
|
// 将完整的帧加入队列,由专门的线程处理 |
|
|
|
|
|
audioDataQueue.offer(data) |
|
|
|
|
|
// // 将接收到的数据添加到缓存中 |
|
|
|
|
|
// synchronized(audioDataBuffer) { |
|
|
|
|
|
// audioDataBuffer.addAll(data.toList()) |
|
|
|
|
|
|
|
|
|
|
|
// // 当缓存达到阈值时,提取完整的帧进行解码 |
|
|
|
|
|
// while (audioDataBuffer.size >= AUDIO_BUFFER_SIZE) { |
|
|
|
|
|
// // 提取一个完整的帧(1280字节) |
|
|
|
|
|
// val frameData = ByteArray(AUDIO_BUFFER_SIZE) |
|
|
|
|
|
// for (i in 0 until AUDIO_BUFFER_SIZE) { |
|
|
|
|
|
// frameData[i] = audioDataBuffer.removeAt(0) |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Log.d(TAG, "缓存达到阈值,提取 ${frameData.size} 字节帧进行解码") |
|
|
|
|
|
// } |
|
|
|
|
|
// } |
|
|
} else { |
|
|
} else { |
|
|
// Log.d(TAG, "Opus解码流未启动,忽略音频数据") |
|
|
// Log.d(TAG, "Opus解码流未启动,忽略音频数据") |
|
|
} |
|
|
} |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "处理音频数据异常: ${e.message}", e) |
|
|
Log.e(TAG, "处理音频数据异常: ${e.message}", e) |
|
|
@ -922,11 +1053,13 @@ object BleService { |
|
|
codecStatus == BleConst.CODEC_CONTROL_A2DP_PLAY || |
|
|
codecStatus == BleConst.CODEC_CONTROL_A2DP_PLAY || |
|
|
codecStatus == BleConst.CODEC_CONTROL_ENCODE_ON |
|
|
codecStatus == BleConst.CODEC_CONTROL_ENCODE_ON |
|
|
) { |
|
|
) { |
|
|
|
|
|
recordfile1!!.closeFile() |
|
|
|
|
|
recordfile1!!.creatingFiles() |
|
|
recordfile!!.closeFile() |
|
|
recordfile!!.closeFile() |
|
|
recordfile!!.creatingFiles() |
|
|
recordfile!!.creatingFiles() |
|
|
} else if (codecStatus == BleConst.CODEC_CONTROL_CLOSE) { |
|
|
} else if (codecStatus == BleConst.CODEC_CONTROL_CLOSE) { |
|
|
recordfile!!.closeFile() |
|
|
recordfile!!.closeFile() |
|
|
|
|
|
recordfile1!!.closeFile() |
|
|
} |
|
|
} |
|
|
// 声道模式描述 |
|
|
// 声道模式描述 |
|
|
val channelDesc = when (channelMode) { |
|
|
val channelDesc = when (channelMode) { |
|
|
@ -1102,11 +1235,16 @@ object BleService { |
|
|
Log.d(TAG, "已停止正在进行的Opus解码流") |
|
|
Log.d(TAG, "已停止正在进行的Opus解码流") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
val option = OpusOption() |
|
|
// 清理音频数据缓存,确保开始时是干净的状态 |
|
|
.setHasHead(hasHeader) |
|
|
synchronized(audioDataBuffer) { |
|
|
.setChannel(channel) |
|
|
audioDataBuffer.clear() |
|
|
.setSampleRate(sampleRate) |
|
|
Log.d(TAG, "开始解码前已清理音频数据缓存") |
|
|
.setPacketSize(packetSize) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
option!!.setHasHead(hasHeader) |
|
|
|
|
|
option!!.setChannel(channel) |
|
|
|
|
|
option!!.setSampleRate(sampleRate) |
|
|
|
|
|
option!!.setPacketSize(packetSize) |
|
|
|
|
|
|
|
|
Log.d(TAG, "准备开始Opus数据流解码, 参数: $option") |
|
|
Log.d(TAG, "准备开始Opus数据流解码, 参数: $option") |
|
|
|
|
|
|
|
|
@ -1115,7 +1253,40 @@ object BleService { |
|
|
override fun onDecodeStream(data: ByteArray?) { |
|
|
override fun onDecodeStream(data: ByteArray?) { |
|
|
if (data != null) { |
|
|
if (data != null) { |
|
|
// Log.d(TAG, "Opus解码数据: ${data.size} bytes") |
|
|
// Log.d(TAG, "Opus解码数据: ${data.size} bytes") |
|
|
notifyAudioDataReceived(data) |
|
|
if (option!!.getChannel() == 2) { |
|
|
|
|
|
|
|
|
|
|
|
//解码数据再重新编码回去 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val sampleCount = data.size / 4 // 每个样本4字节(左右声道各2字节) |
|
|
|
|
|
val leftBuffer = ByteArray(sampleCount * 2) // 左声道缓冲区 |
|
|
|
|
|
val rightBuffer = ByteArray(sampleCount * 2) // 右声道缓冲区 |
|
|
|
|
|
|
|
|
|
|
|
// 拆分交错的左右声道数据 |
|
|
|
|
|
for (i in 0 until sampleCount) { |
|
|
|
|
|
val stereoIndex = i * 4 |
|
|
|
|
|
val monoIndex = i * 2 |
|
|
|
|
|
|
|
|
|
|
|
// 左声道(低位字节在前,高位字节在后) |
|
|
|
|
|
leftBuffer[monoIndex] = data[stereoIndex] |
|
|
|
|
|
leftBuffer[monoIndex + 1] = data[stereoIndex + 1] |
|
|
|
|
|
|
|
|
|
|
|
// 右声道 |
|
|
|
|
|
rightBuffer[monoIndex] = data[stereoIndex + 2] |
|
|
|
|
|
rightBuffer[monoIndex + 1] = data[stereoIndex + 3] |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// //重新编码 |
|
|
|
|
|
// opusManager?.writeEncodeStream(rightBuffer) |
|
|
|
|
|
//notifyAudioDataReceived1(leftBuffer) |
|
|
|
|
|
//回调 |
|
|
|
|
|
notifyAudioDataReceived(rightBuffer)//对方的 |
|
|
|
|
|
notifyAudioDataReceived1(leftBuffer)//自己的 |
|
|
|
|
|
} else if (option!!.getChannel() == 1) { |
|
|
|
|
|
notifyAudioDataReceived(data) |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.e(TAG, "Opus解码数据错误: ${data.size} bytes") |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1143,6 +1314,13 @@ object BleService { |
|
|
private fun stopOpusStreamDecoding(): Boolean { |
|
|
private fun stopOpusStreamDecoding(): Boolean { |
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
opusManager?.stopDecodeStream() |
|
|
opusManager?.stopDecodeStream() |
|
|
|
|
|
|
|
|
|
|
|
// 清理音频数据缓存 |
|
|
|
|
|
synchronized(audioDataBuffer) { |
|
|
|
|
|
audioDataBuffer.clear() |
|
|
|
|
|
Log.d(TAG, "已清理音频数据缓存") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Log.i(TAG, "已停止Opus数据流解码") |
|
|
Log.i(TAG, "已停止Opus数据流解码") |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
@ -1150,7 +1328,268 @@ object BleService { |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
|
|
|
// Opus 编码相关 |
|
|
|
|
|
// ====================================================================================================== |
|
|
|
|
|
|
|
|
|
|
|
private fun startOpusEncodeStream(): Boolean { |
|
|
|
|
|
if (opusManager == null) { |
|
|
|
|
|
Log.e(TAG, "OpusManager未初始化,无法开始编码") |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果已经在编码流,先停止 |
|
|
|
|
|
if (opusManager?.isEncodeStream == true) { |
|
|
|
|
|
Log.d(TAG, "Opus编码流已在运行") |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 启动音频发送线程 |
|
|
|
|
|
startAudioSendThread() |
|
|
|
|
|
|
|
|
|
|
|
var streamStartedSuccessfully = false |
|
|
|
|
|
opusManager?.startEncodeStream(object : OnEncodeStreamCallback { |
|
|
|
|
|
override fun onEncodeStream(data: ByteArray?) { |
|
|
|
|
|
if (data != null) { |
|
|
|
|
|
// 编码完成的数据处理: |
|
|
|
|
|
// 1. 保存到WAV文件 |
|
|
|
|
|
recordfile1?.saveAudioDataToWav(data) |
|
|
|
|
|
// 2. 将编码后的数据加入发送队列进行分块发送 |
|
|
|
|
|
addAudioDataToSendQueue(data) |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "编码回调收到空数据") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun onStart() { |
|
|
|
|
|
streamStartedSuccessfully = true |
|
|
|
|
|
Log.i(TAG, "Opus数据流编码已开始") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun onComplete(outPath: String?) { |
|
|
|
|
|
Log.i(TAG, "Opus数据流编码完成: $outPath") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun onError(code: Int, message: String?) { |
|
|
|
|
|
Log.e(TAG, "Opus数据流编码错误: [$code] $message") |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun stopOpusEncodeStream(): Boolean { |
|
|
|
|
|
if (opusManager?.isEncodeStream == true) { |
|
|
|
|
|
opusManager?.stopEncodeStream() |
|
|
|
|
|
stopAudioSendThread() // 停止音频发送线程 |
|
|
|
|
|
Log.i(TAG, "已停止Opus数据流编码") |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
Log.d(TAG, "Opus数据流未在编码或OpusManager未初始化") |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 将音频数据添加到发送队列 |
|
|
|
|
|
* 只有当缓冲区达到80字节时才发送数据 |
|
|
|
|
|
* @param data 音频数据字节数组 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun addAudioDataToSendQueue(data: ByteArray) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 将新数据添加到缓冲区 |
|
|
|
|
|
audioBuffer.addAll(data.toList()) |
|
|
|
|
|
|
|
|
|
|
|
// 当缓冲区达到80字节时,发送数据 |
|
|
|
|
|
while (audioBuffer.size >= AUDIO_CHUNK_SIZE) { |
|
|
|
|
|
// 取出80字节数据 |
|
|
|
|
|
val chunk = ByteArray(AUDIO_CHUNK_SIZE) |
|
|
|
|
|
for (i in 0 until AUDIO_CHUNK_SIZE) { |
|
|
|
|
|
chunk[i] = audioBuffer.removeAt(0) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 将数据加入发送队列 |
|
|
|
|
|
if (!audioSendQueue.offer(chunk)) { |
|
|
|
|
|
Log.w(TAG, "音频发送队列已满,丢弃数据块") |
|
|
|
|
|
break |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Log.d(TAG, "音频数据已加入缓冲区,当前缓冲区大小: ${audioBuffer.size} 字节") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "处理音频数据异常: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 启动音频数据发送线程 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun startAudioSendThread() { |
|
|
|
|
|
if (audioSendThread?.isAlive == true) { |
|
|
|
|
|
Log.d(TAG, "音频发送线程已在运行") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isAudioSending.set(true) |
|
|
|
|
|
audioSendThread = Thread { |
|
|
|
|
|
Log.i(TAG, "音频发送线程已启动") |
|
|
|
|
|
|
|
|
|
|
|
while (isAudioSending.get() && !Thread.currentThread().isInterrupted) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 从队列中取出音频数据块 |
|
|
|
|
|
val audioChunk = audioSendQueue.poll(100, TimeUnit.MILLISECONDS) |
|
|
|
|
|
|
|
|
|
|
|
if (audioChunk != null) { |
|
|
|
|
|
// 发送音频数据块 |
|
|
|
|
|
sendAudioChunk(audioChunk) |
|
|
|
|
|
|
|
|
|
|
|
// 控制发送频率,避免蓝牙缓冲区溢出 |
|
|
|
|
|
Thread.sleep(AUDIO_SEND_INTERVAL) |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e: InterruptedException) { |
|
|
|
|
|
Log.d(TAG, "音频发送线程被中断") |
|
|
|
|
|
break |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "音频发送线程异常: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "音频发送线程已停止") |
|
|
|
|
|
}.apply { |
|
|
|
|
|
name = "AudioSendThread" |
|
|
|
|
|
start() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 停止音频数据发送线程 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun stopAudioSendThread() { |
|
|
|
|
|
isAudioSending.set(false) |
|
|
|
|
|
audioSendThread?.interrupt() |
|
|
|
|
|
audioSendQueue.clear() |
|
|
|
|
|
audioBuffer.clear() // 清空音频缓冲区 |
|
|
|
|
|
Log.i(TAG, "音频发送线程已停止,队列和缓冲区已清空") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 发送音频数据块到设备(带重发机制) |
|
|
|
|
|
* @param chunk 要发送的音频数据块 |
|
|
|
|
|
* @param retryCount 当前重试次数 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun sendAudioChunk(chunk: ByteArray, retryCount: Int = 0) { |
|
|
|
|
|
try { |
|
|
|
|
|
if (callWriteChar == null || bluetoothGatt == null) { |
|
|
|
|
|
Log.e(TAG, "蓝牙连接或特征值未准备就绪") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 在主线程中执行蓝牙写入操作 |
|
|
|
|
|
audioSendHandler.post { |
|
|
|
|
|
try { |
|
|
|
|
|
callWriteChar?.value = chunk |
|
|
|
|
|
val isSuccess = bluetoothGatt?.writeCharacteristic(callWriteChar) |
|
|
|
|
|
|
|
|
|
|
|
if (isSuccess == true) { |
|
|
|
|
|
// 累加发送成功的字节数 |
|
|
|
|
|
bytesSentInCurrentSecond += chunk.size |
|
|
|
|
|
Log.d(TAG, "成功发送音频数据块,大小: ${chunk.size} 字节") |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.e(TAG, "发送音频数据块失败,大小: ${chunk.size} 字节") |
|
|
|
|
|
// 详细的失败原因分析 |
|
|
|
|
|
val failureReason = StringBuilder("发送音频数据块失败,大小: ${chunk.size} 字节") |
|
|
|
|
|
|
|
|
|
|
|
// 检查蓝牙连接状态 |
|
|
|
|
|
if (bluetoothGatt == null) { |
|
|
|
|
|
failureReason.append(" - 原因: BluetoothGatt为空") |
|
|
|
|
|
} else { |
|
|
|
|
|
// 检查连接状态 |
|
|
|
|
|
val connectionState = bluetoothManager.getConnectionState( |
|
|
|
|
|
bluetoothGatt!!.device, |
|
|
|
|
|
BluetoothProfile.GATT |
|
|
|
|
|
) |
|
|
|
|
|
when (connectionState) { |
|
|
|
|
|
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)") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查蓝牙适配器状态 |
|
|
|
|
|
if (bluetoothAdapter?.isEnabled != true) { |
|
|
|
|
|
failureReason.append(" - 蓝牙适配器未启用") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Log.e(TAG, failureReason.toString()) |
|
|
|
|
|
|
|
|
|
|
|
// // 实现重发机制 |
|
|
|
|
|
// if (retryCount < MAX_RETRY_COUNT) { |
|
|
|
|
|
// Log.w(TAG, "准备重发音频数据块,重试次数: ${retryCount + 1}/$MAX_RETRY_COUNT") |
|
|
|
|
|
|
|
|
|
|
|
// // 延迟后重发 |
|
|
|
|
|
// audioSendHandler.postDelayed({ |
|
|
|
|
|
// val isSuccess = bluetoothGatt?.writeCharacteristic(callWriteChar) |
|
|
|
|
|
|
|
|
|
|
|
// if (isSuccess == true) { |
|
|
|
|
|
// Log.d(TAG, "重发音频数据块成功") |
|
|
|
|
|
// } else { |
|
|
|
|
|
// Log.e(TAG, "重发音频数据块失败") |
|
|
|
|
|
// } |
|
|
|
|
|
// }, RETRY_DELAY) |
|
|
|
|
|
// } else { |
|
|
|
|
|
// Log.e(TAG, "音频数据块发送失败,已达到最大重试次数($MAX_RETRY_COUNT),丢弃数据块") |
|
|
|
|
|
// // 可以选择将失败的数据块记录到日志或进行其他处理 |
|
|
|
|
|
// } |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "发送音频数据块异常: ${e.message}", e) |
|
|
|
|
|
|
|
|
|
|
|
// // 异常情况下也进行重发 |
|
|
|
|
|
// if (retryCount < MAX_RETRY_COUNT) { |
|
|
|
|
|
// Log.w(TAG, "发送异常,准备重发音频数据块,重试次数: ${retryCount + 1}/$MAX_RETRY_COUNT") |
|
|
|
|
|
// audioSendHandler.postDelayed({ |
|
|
|
|
|
// val isSuccess = bluetoothGatt?.writeCharacteristic(callWriteChar) |
|
|
|
|
|
|
|
|
|
|
|
// if (isSuccess == true) { |
|
|
|
|
|
// Log.d(TAG, "重发音频数据块成功") |
|
|
|
|
|
// } else { |
|
|
|
|
|
// Log.e(TAG, "重发音频数据块失败") |
|
|
|
|
|
// } |
|
|
|
|
|
// }, RETRY_DELAY) |
|
|
|
|
|
// } else { |
|
|
|
|
|
// Log.e(TAG, "音频数据块发送异常,已达到最大重试次数($MAX_RETRY_COUNT),丢弃数据块") |
|
|
|
|
|
// } |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "发送音频数据块异常: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
// 公开的命令接口 |
|
|
// 公开的命令接口 |
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
@ -1217,7 +1656,7 @@ object BleService { |
|
|
// ) |
|
|
// ) |
|
|
startOpusStreamDecoding(false, 1, 16000, 40) |
|
|
startOpusStreamDecoding(false, 1, 16000, 40) |
|
|
|
|
|
|
|
|
Log.i(TAG, "打开解码0xA2") |
|
|
Log.i(TAG, "打开解码0xA1") |
|
|
return sendCommand( |
|
|
return sendCommand( |
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( |
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( |
|
|
BleConst.CODEC_CONTROL_DECODE_ON.toByte(), |
|
|
BleConst.CODEC_CONTROL_DECODE_ON.toByte(), |
|
|
@ -1231,8 +1670,10 @@ object BleService { |
|
|
* 控制编解码 - 打开解码 |
|
|
* 控制编解码 - 打开解码 |
|
|
*/ |
|
|
*/ |
|
|
fun openA2DPDecoder(): Boolean { |
|
|
fun openA2DPDecoder(): Boolean { |
|
|
Log.i(TAG, "打开编码 0xA1") |
|
|
Log.i(TAG, "打开编码 0xA2") |
|
|
startOpusStreamDecoding(false, 1, 16000, 80) |
|
|
startOpusEncodeStream() |
|
|
|
|
|
//双声道 80字节 |
|
|
|
|
|
startOpusStreamDecoding(false, 2, 16000, 80) |
|
|
// Log.i(TAG, "打开解码...") |
|
|
// Log.i(TAG, "打开解码...") |
|
|
return sendCommand( |
|
|
return sendCommand( |
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( |
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( |
|
|
@ -1323,7 +1764,7 @@ object BleService { |
|
|
* 连接状态检查 |
|
|
* 连接状态检查 |
|
|
*/ |
|
|
*/ |
|
|
private fun checkConn(): Boolean = |
|
|
private fun checkConn(): Boolean = |
|
|
bluetoothGatt != null && writeChar != null && |
|
|
bluetoothGatt != null && writeChar != null && callWriteChar != null && |
|
|
connectionState.value == BleConst.STATE_CONNECTED |
|
|
connectionState.value == BleConst.STATE_CONNECTED |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -1351,14 +1792,28 @@ object BleService { |
|
|
stopScan() |
|
|
stopScan() |
|
|
scanHandler.removeCallbacksAndMessages(null) |
|
|
scanHandler.removeCallbacksAndMessages(null) |
|
|
disconnectGatt() |
|
|
disconnectGatt() |
|
|
// 释放OpusManager |
|
|
// 停止音频队列处理线程 |
|
|
opusManager?.let { |
|
|
audioQueueProcessorThread?.interrupt() |
|
|
if (it.isDecodeStream) { |
|
|
audioQueueProcessorThread = null |
|
|
it.stopDecodeStream() |
|
|
audioDataQueue.clear() |
|
|
} |
|
|
|
|
|
it.release() |
|
|
// 停止音频发送线程 |
|
|
|
|
|
stopAudioSendThread() |
|
|
|
|
|
|
|
|
|
|
|
// 清理音频数据缓存 |
|
|
|
|
|
synchronized(audioDataBuffer) { |
|
|
|
|
|
audioDataBuffer.clear() |
|
|
|
|
|
Log.d(TAG, "已清理音频数据缓存") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "音频解码线程和队列处理线程已清理") |
|
|
|
|
|
|
|
|
|
|
|
// 释放OpusManager |
|
|
|
|
|
stopOpusEncodeStream() |
|
|
|
|
|
stopOpusStreamDecoding() |
|
|
|
|
|
opusManager?.release() |
|
|
opusManager = null |
|
|
opusManager = null |
|
|
|
|
|
option = null |
|
|
Log.d(TAG, "OpusManager已释放") |
|
|
Log.d(TAG, "OpusManager已释放") |
|
|
isInitialized = false // 标记为未初始化 |
|
|
isInitialized = false // 标记为未初始化 |
|
|
} |
|
|
} |
|
|
@ -1424,8 +1879,7 @@ object BleService { |
|
|
isReply = false |
|
|
isReply = false |
|
|
replyTimeoutHandler.postDelayed(replyTimeoutRunnable, 1000) // 设置1秒超时 |
|
|
replyTimeoutHandler.postDelayed(replyTimeoutRunnable, 1000) // 设置1秒超时 |
|
|
return true |
|
|
return true |
|
|
} else |
|
|
} else { |
|
|
{ |
|
|
|
|
|
commandQueue.poll() |
|
|
commandQueue.poll() |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
@ -1511,6 +1965,18 @@ object BleService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
|
|
|
* 向所有回调监听器分发音频数据 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun notifyAudioDataReceived1(data: ByteArray) { |
|
|
|
|
|
for (callback in callbacks) { |
|
|
|
|
|
try { |
|
|
|
|
|
callback.onAudioDataReceived1(data) |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "分发音频数据回调异常", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 向所有回调监听器分发唤醒信号 |
|
|
* 向所有回调监听器分发唤醒信号 |
|
|
@ -1544,6 +2010,46 @@ object BleService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取PHY类型的字符串描述 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun getPhyString(phy: Int): String { |
|
|
|
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
|
|
when (phy) { |
|
|
|
|
|
BluetoothDevice.PHY_LE_1M -> "1M" |
|
|
|
|
|
BluetoothDevice.PHY_LE_2M -> "2M" |
|
|
|
|
|
BluetoothDevice.PHY_LE_CODED -> "Coded" |
|
|
|
|
|
else -> "Unknown($phy)" |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
"Unknown($phy)" |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 启动音频队列处理 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun startAudioQueueProcessing() { |
|
|
|
|
|
audioQueueProcessorThread = Thread { |
|
|
|
|
|
while (!Thread.currentThread().isInterrupted) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 从队列中取出音频数据进行解码 |
|
|
|
|
|
val audioData = audioDataQueue.take() // 阻塞等待数据 |
|
|
|
|
|
|
|
|
|
|
|
opusManager?.writeAudioStream(audioData) |
|
|
|
|
|
} catch (e: InterruptedException) { |
|
|
|
|
|
Log.d(TAG, "音频队列处理线程被中断") |
|
|
|
|
|
break |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "音频队列处理异常: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}.apply { |
|
|
|
|
|
name = "AudioQueueProcessor" |
|
|
|
|
|
start() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|