|
|
|
@ -23,7 +23,12 @@ import androidx.core.content.ContextCompat |
|
|
|
import java.util.concurrent.ConcurrentHashMap |
|
|
|
import android.content.pm.ServiceInfo |
|
|
|
import java.util.concurrent.CopyOnWriteArrayList |
|
|
|
import com.yunqiinnovation.jl_opus.JlOpusPlugin |
|
|
|
// Import Jieli Opus SDK classes |
|
|
|
import com.jieli.jl_audio_decode.opus.model.OpusOption |
|
|
|
import com.jieli.jl_audio_decode.callback.OnDecodeStreamCallback |
|
|
|
import com.jieli.jl_audio_decode.callback.OnStateCallback |
|
|
|
import com.jieli.jl_audio_decode.opus.OpusManager |
|
|
|
import com.jieli.jl_audio_decode.exceptions.OpusException |
|
|
|
|
|
|
|
/** |
|
|
|
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 |
|
|
|
@ -86,8 +91,10 @@ object BleService { |
|
|
|
// 回调集合 - 使用线程安全的集合存储所有回调 |
|
|
|
private val callbacks = CopyOnWriteArrayList<Callback>() |
|
|
|
|
|
|
|
// 获取插件实例 |
|
|
|
private val opusPlugin = JlOpusPlugin() |
|
|
|
// Opus解码器实例 |
|
|
|
private var opusManager: OpusManager? = null |
|
|
|
private val mainHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
|
|
|
|
// 初始化状态 |
|
|
|
private var isInitialized = false |
|
|
|
@ -112,6 +119,16 @@ object BleService { |
|
|
|
// 初始化Handler |
|
|
|
scanHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
// 初始化OpusManager |
|
|
|
try { |
|
|
|
opusManager = OpusManager() |
|
|
|
FileLogger.d(TAG, "OpusManager初始化成功") |
|
|
|
} catch (e: OpusException) { |
|
|
|
FileLogger.e(TAG, "OpusManager初始化失败: ${e.message}", e) |
|
|
|
// 根据需要决定是否因为Opus初始化失败而返回false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isInitialized = true |
|
|
|
FileLogger.d(TAG, "BLE服务初始化成功") |
|
|
|
return true |
|
|
|
@ -424,13 +441,7 @@ object BleService { |
|
|
|
// 设置主服务的通知 |
|
|
|
setupNotifications(g, notifyChar) |
|
|
|
//开启解码 |
|
|
|
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object : |
|
|
|
JlOpusPlugin.CallbackOpus { |
|
|
|
override fun onAudioDataReceivedOpus(pcmData: ByteArray) { |
|
|
|
// 回调给上层 |
|
|
|
notifyAudioDataReceived(pcmData) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { |
|
|
|
@ -512,18 +523,21 @@ object BleService { |
|
|
|
|
|
|
|
/** |
|
|
|
* 处理音频数据 |
|
|
|
* @param data 音频数据 |
|
|
|
* @param data 音频数据 (Opus) |
|
|
|
*/ |
|
|
|
private fun processAudioData(data: ByteArray) { |
|
|
|
try { |
|
|
|
|
|
|
|
opusPlugin?.handleDecodeRequest(data) |
|
|
|
|
|
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
|
opusManager?.writeAudioStream(data) |
|
|
|
} else { |
|
|
|
// FileLogger.d(TAG, "Opus解码流未启动,忽略音频数据") |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 处理设备响应数据 - 帧头为0xBB |
|
|
|
*/ |
|
|
|
@ -841,7 +855,7 @@ object BleService { |
|
|
|
|
|
|
|
BleConst.CMD_WAKE_UP -> { |
|
|
|
FileLogger.i(TAG, "收到唤醒指令") |
|
|
|
// 接收到唤醒信号,打开编码器 |
|
|
|
// 接收到唤醒信号,打开编码器 (设备侧) |
|
|
|
openEncoder() |
|
|
|
notifyWakeupSignalReceived() |
|
|
|
|
|
|
|
@ -862,6 +876,75 @@ object BleService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
|
// Opus 解码相关 |
|
|
|
// ====================================================================================================== |
|
|
|
|
|
|
|
private fun startOpusStreamDecoding( |
|
|
|
hasHeader: Boolean = false, // 通常BLE传输的Opus没有文件头 |
|
|
|
channel: Int = 1, |
|
|
|
sampleRate: Int = 16000, // 确认设备端Opus编码采样率 |
|
|
|
packetSize: Int = 40 // 确认设备端Opus编码帧长 |
|
|
|
): Boolean { |
|
|
|
if (opusManager == null) { |
|
|
|
FileLogger.e(TAG, "OpusManager未初始化,无法开始解码") |
|
|
|
return false |
|
|
|
} |
|
|
|
// 如果已经在解码流,先停止 |
|
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
|
opusManager?.stopDecodeStream() |
|
|
|
FileLogger.d(TAG, "已停止正在进行的Opus解码流") |
|
|
|
} |
|
|
|
|
|
|
|
val option = OpusOption() |
|
|
|
.setHasHead(hasHeader) |
|
|
|
.setChannel(channel) |
|
|
|
.setSampleRate(sampleRate) |
|
|
|
.setPacketSize(packetSize) |
|
|
|
|
|
|
|
FileLogger.d(TAG, "准备开始Opus数据流解码, 参数: $option") |
|
|
|
|
|
|
|
var streamStartedSuccessfully = false |
|
|
|
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { |
|
|
|
override fun onDecodeStream(data: ByteArray?) { |
|
|
|
if (data != null) { |
|
|
|
// FileLogger.d(TAG, "Opus解码数据: ${data.size} bytes") |
|
|
|
mainHandler.post { notifyAudioDataReceived(data) } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onStart() { |
|
|
|
streamStartedSuccessfully = true |
|
|
|
FileLogger.i(TAG, "Opus数据流解码已开始") |
|
|
|
// 可以通过回调通知上层解码已开始 |
|
|
|
} |
|
|
|
|
|
|
|
override fun onComplete(outPath: String?) { |
|
|
|
FileLogger.i(TAG, "Opus数据流解码完成: $outPath (通常流式解码不会调用此方法)") |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(code: Int, message: String?) { |
|
|
|
FileLogger.e(TAG, "Opus数据流解码错误: [$code] $message") |
|
|
|
// 可以通过回调通知上层解码错误 |
|
|
|
} |
|
|
|
}) |
|
|
|
// 注意:startDecodeStream是异步启动的,这里的返回值可能不准确反映是否真的成功启动。 |
|
|
|
// onStart回调才是真正启动成功的标志。为了简单,我们暂时认为调用即尝试启动。 |
|
|
|
// 后续可以改造为返回Future或通过回调传递启动结果。 |
|
|
|
return true // 暂定为调用即成功 |
|
|
|
} |
|
|
|
|
|
|
|
private fun stopOpusStreamDecoding(): Boolean { |
|
|
|
if (opusManager?.isDecodeStream == true) { |
|
|
|
opusManager?.stopDecodeStream() |
|
|
|
FileLogger.i(TAG, "已停止Opus数据流解码") |
|
|
|
return true |
|
|
|
} |
|
|
|
FileLogger.d(TAG, "Opus数据流未在解码或OpusManager未初始化") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
|
// 公开的命令接口 |
|
|
|
// ====================================================================================================== |
|
|
|
@ -902,11 +985,13 @@ object BleService { |
|
|
|
*/ |
|
|
|
fun closeCodec(): Boolean { |
|
|
|
FileLogger.i(TAG, "关闭编解码...") |
|
|
|
|
|
|
|
stopOpusStreamDecoding() // 停止Opus解码流 |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), |
|
|
|
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte(), |
|
|
|
|
|
|
|
BleConst.AUDIO_CHANNEL_RIGHT.toByte()) |
|
|
|
BleConst.AUDIO_CHANNEL_RIGHT.toByte()) // 或其他默认声道 |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
@ -915,10 +1000,7 @@ object BleService { |
|
|
|
*/ |
|
|
|
fun openDecoder(): Boolean { |
|
|
|
FileLogger.i(TAG, "打开解码...") |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), |
|
|
|
byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte()) |
|
|
|
) |
|
|
|
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte())) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -933,16 +1015,33 @@ object BleService { |
|
|
|
* 控制编解码 - 打开编码 |
|
|
|
*/ |
|
|
|
fun openEncoder(): Boolean { |
|
|
|
FileLogger.i(TAG, "打开编码...") |
|
|
|
FileLogger.i(TAG, "打开编码 (设备侧)...") |
|
|
|
|
|
|
|
val opusStarted = startOpusStreamDecoding( |
|
|
|
hasHeader = false, // BLE传输的Opus数据通常无文件头 |
|
|
|
channel = 1, // 单声道 |
|
|
|
sampleRate = 16000,// Opus推荐16kHz或更高 |
|
|
|
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM) |
|
|
|
// packetSize for Opus is typically in ms, e.g. 20, 40, 60. |
|
|
|
// The library might expect this in a different unit or derive it. |
|
|
|
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// 注意:此方法通常是控制设备端开始编码并发送数据。 |
|
|
|
// App侧通常是打开解码器(openDecoder)来接收和处理这些数据。 |
|
|
|
// 如果App也需要编码(例如发送语音到设备),则需要实现Opus编码逻辑。 |
|
|
|
// 目前仅发送命令通知设备开始编码。 |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), |
|
|
|
byteArrayOf( |
|
|
|
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(), |
|
|
|
BleConst.AUDIO_CHANNEL_RIGHT.toByte() |
|
|
|
BleConst.AUDIO_CHANNEL_RIGHT.toByte() // 或设备期望的声道 |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 设置声道模式 |
|
|
|
* @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO |
|
|
|
@ -1026,8 +1125,19 @@ object BleService { |
|
|
|
stopScan() |
|
|
|
scanHandler.removeCallbacksAndMessages(null) |
|
|
|
disconnectGatt() |
|
|
|
// 释放OpusManager |
|
|
|
opusManager?.let { |
|
|
|
if (it.isDecodeStream) { |
|
|
|
it.stopDecodeStream() |
|
|
|
} |
|
|
|
it.release() |
|
|
|
} |
|
|
|
opusManager = null |
|
|
|
FileLogger.d(TAG, "OpusManager已释放") |
|
|
|
isInitialized = false // 标记为未初始化 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
|
// 协议命令实现 |
|
|
|
// ====================================================================================================== |
|
|
|
|