2 changed files with 597 additions and 216 deletions
@ -0,0 +1,491 @@ |
|||
package com.yunqiinnovation.ble_service |
|||
|
|||
import android.os.Handler |
|||
import android.os.Looper |
|||
import android.util.Log |
|||
import com.jieli.jl_audio_decode.opus.model.OpusOption |
|||
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.exceptions.OpusException |
|||
import java.util.concurrent.LinkedBlockingQueue |
|||
import java.util.concurrent.atomic.AtomicBoolean |
|||
|
|||
/** |
|||
* Opus音频编解码管理器 |
|||
* 负责处理音频数据的Opus编码和解码功能 |
|||
* |
|||
* 功能包括: |
|||
* 1. Opus解码流管理 |
|||
* 2. Opus编码流管理 |
|||
* 3. 音频数据缓冲和处理 |
|||
* 4. 编解码状态管理 |
|||
*/ |
|||
class OpusAudioManager { |
|||
|
|||
companion object { |
|||
private const val TAG = "OpusAudioManager" |
|||
|
|||
// 音频数据分块发送的常量 |
|||
private const val AUDIO_CHUNK_SIZE = 120 // 每次发送120字节 |
|||
private const val AUDIO_SEND_INTERVAL = 60L // 发送间隔60ms |
|||
|
|||
// 重发机制相关常量 |
|||
private const val MAX_RETRY_COUNT = 1 // 最大重试次数 |
|||
private const val RETRY_DELAY = 30L // 重试延迟时间(毫秒) |
|||
} |
|||
|
|||
// ====================================================================================================== |
|||
// 回调接口定义 |
|||
// ====================================================================================================== |
|||
|
|||
/** |
|||
* Opus音频管理器回调接口 |
|||
*/ |
|||
interface OpusAudioCallback { |
|||
/** |
|||
* 音频数据解码完成回调 |
|||
* @param data 解码后的音频数据 |
|||
* @param channel 音频通道数 |
|||
*/ |
|||
fun onAudioDataDecoded(data: ByteArray, channel: Int) |
|||
|
|||
/** |
|||
* 音频数据编码完成回调 |
|||
* @param data 编码后的音频数据 |
|||
*/ |
|||
fun onAudioDataEncoded(data: ByteArray) |
|||
|
|||
/** |
|||
* 解码流状态变化回调 |
|||
* @param isStarted 是否已启动 |
|||
*/ |
|||
fun onDecodeStreamStateChanged(isStarted: Boolean) |
|||
|
|||
/** |
|||
* 编码流状态变化回调 |
|||
* @param isStarted 是否已启动 |
|||
*/ |
|||
fun onEncodeStreamStateChanged(isStarted: Boolean) |
|||
|
|||
/** |
|||
* 错误回调 |
|||
* @param errorType 错误类型 (decode/encode) |
|||
* @param code 错误代码 |
|||
* @param message 错误信息 |
|||
*/ |
|||
fun onError(errorType: String, code: Int, message: String?) |
|||
} |
|||
|
|||
// ====================================================================================================== |
|||
// 成员变量 |
|||
// ====================================================================================================== |
|||
|
|||
// Opus解码器实例 |
|||
private var opusManager: OpusManager? = null |
|||
private var option: OpusOption? = null |
|||
private val mainHandler = Handler(Looper.getMainLooper()) |
|||
|
|||
// 解码音频数据队列和处理线程 |
|||
private val audioDataQueue = LinkedBlockingQueue<ByteArray>() |
|||
private var audioQueueProcessorThread: Thread? = null |
|||
|
|||
// 音频数据缓存 |
|||
private val audioDataBuffer = mutableListOf<Byte>() |
|||
|
|||
// 音频数据发送相关 |
|||
private val audioSendQueue = LinkedBlockingQueue<ByteArray>() |
|||
private var audioSendThread: Thread? = null |
|||
private val audioSendHandler = Handler(Looper.getMainLooper()) |
|||
private val isAudioSending = AtomicBoolean(false) |
|||
|
|||
// 音频数据缓冲区,用于累积数据到指定字节再发送 |
|||
private val audioBuffer = mutableListOf<Byte>() |
|||
|
|||
// 回调接口 |
|||
private var callback: OpusAudioCallback? = null |
|||
|
|||
// 初始化状态 |
|||
private var isInitialized = false |
|||
|
|||
// ====================================================================================================== |
|||
// 初始化和配置方法 |
|||
// ====================================================================================================== |
|||
|
|||
/** |
|||
* 初始化Opus音频管理器 |
|||
* @return 是否初始化成功 |
|||
*/ |
|||
fun initialize(): Boolean { |
|||
if (isInitialized) return true |
|||
|
|||
try { |
|||
// 初始化OpusManager和OpusOption |
|||
opusManager = OpusManager() |
|||
option = OpusOption() |
|||
|
|||
// 启动队列处理 |
|||
startAudioQueueProcessing() |
|||
|
|||
isInitialized = true |
|||
Log.d(TAG, "OpusAudioManager初始化成功") |
|||
return true |
|||
} catch (e: OpusException) { |
|||
Log.e(TAG, "OpusAudioManager初始化失败: ${e.message}", e) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置回调接口 |
|||
* @param callback 回调接口实例 |
|||
*/ |
|||
fun setCallback(callback: OpusAudioCallback?) { |
|||
this.callback = callback |
|||
} |
|||
|
|||
/** |
|||
* 清理资源 |
|||
*/ |
|||
fun cleanup() { |
|||
Log.d(TAG, "开始清理OpusAudioManager资源") |
|||
|
|||
// 停止编解码流 |
|||
stopDecodeStream() |
|||
stopEncodeStream() |
|||
|
|||
// 停止音频发送线程 |
|||
stopAudioSendThread() |
|||
|
|||
// 停止音频队列处理线程 |
|||
audioQueueProcessorThread?.interrupt() |
|||
audioQueueProcessorThread = null |
|||
|
|||
// 清理缓存 |
|||
synchronized(audioDataBuffer) { |
|||
audioDataBuffer.clear() |
|||
} |
|||
synchronized(audioBuffer) { |
|||
audioBuffer.clear() |
|||
} |
|||
audioDataQueue.clear() |
|||
audioSendQueue.clear() |
|||
|
|||
// 清理Opus相关资源 |
|||
opusManager = null |
|||
option = null |
|||
callback = null |
|||
|
|||
isInitialized = false |
|||
Log.d(TAG, "OpusAudioManager资源清理完成") |
|||
} |
|||
|
|||
// ====================================================================================================== |
|||
// Opus解码相关方法 |
|||
// ====================================================================================================== |
|||
|
|||
/** |
|||
* 开始Opus数据流解码 |
|||
* @param hasHeader 是否包含文件头,通常BLE传输的Opus没有文件头 |
|||
* @param channel 音频通道数 |
|||
* @param sampleRate 采样率,确认设备端Opus编码采样率 |
|||
* @param packetSize 包大小,确认设备端Opus编码帧长 |
|||
* @return 是否成功开始解码 |
|||
*/ |
|||
fun startDecodeStream( |
|||
hasHeader: Boolean = false, |
|||
channel: Int = 1, |
|||
sampleRate: Int = 16000, |
|||
packetSize: Int = 40 |
|||
): Boolean { |
|||
if (!isInitialized || opusManager == null) { |
|||
Log.e(TAG, "OpusManager未初始化,无法开始解码") |
|||
return false |
|||
} |
|||
|
|||
// 如果已经在解码流,先停止 |
|||
if (opusManager?.isDecodeStream == true) { |
|||
opusManager?.stopDecodeStream() |
|||
Log.d(TAG, "已停止正在进行的Opus解码流") |
|||
} |
|||
|
|||
// 清理音频数据缓存,确保开始时是干净的状态 |
|||
synchronized(audioDataBuffer) { |
|||
audioDataBuffer.clear() |
|||
Log.d(TAG, "开始解码前已清理音频数据缓存") |
|||
} |
|||
|
|||
// 配置解码参数 |
|||
option?.apply { |
|||
setHasHead(hasHeader) |
|||
setChannel(channel) |
|||
setSampleRate(sampleRate) |
|||
setPacketSize(packetSize) |
|||
} |
|||
|
|||
Log.d(TAG, "准备开始Opus数据流解码, 参数: hasHeader=$hasHeader, channel=$channel, sampleRate=$sampleRate, packetSize=$packetSize") |
|||
|
|||
try { |
|||
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { |
|||
override fun onDecodeStream(data: ByteArray?) { |
|||
if (data != null) { |
|||
val channelCount = option?.getChannel() ?: 1 |
|||
if (channelCount >= 1) { |
|||
callback?.onAudioDataDecoded(data, channelCount) |
|||
} else { |
|||
Log.e(TAG, "Opus解码数据错误: ${data.size} bytes") |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onStart() { |
|||
Log.i(TAG, "Opus数据流解码已开始") |
|||
callback?.onDecodeStreamStateChanged(true) |
|||
} |
|||
|
|||
override fun onComplete(outPath: String?) { |
|||
Log.i(TAG, "Opus数据流解码完成: $outPath") |
|||
callback?.onDecodeStreamStateChanged(false) |
|||
} |
|||
|
|||
override fun onError(code: Int, message: String?) { |
|||
Log.e(TAG, "Opus数据流解码错误: [$code] $message") |
|||
callback?.onError("decode", code, message) |
|||
callback?.onDecodeStreamStateChanged(false) |
|||
} |
|||
}) |
|||
|
|||
return true |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "启动Opus解码流失败: ${e.message}", e) |
|||
callback?.onError("decode", -1, e.message) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 停止Opus数据流解码 |
|||
* @return 是否成功停止解码 |
|||
*/ |
|||
fun stopDecodeStream(): Boolean { |
|||
if (opusManager?.isDecodeStream == true) { |
|||
opusManager?.stopDecodeStream() |
|||
|
|||
// 清理音频数据缓存 |
|||
synchronized(audioDataBuffer) { |
|||
audioDataBuffer.clear() |
|||
Log.d(TAG, "已清理音频数据缓存") |
|||
} |
|||
|
|||
Log.i(TAG, "已停止Opus数据流解码") |
|||
callback?.onDecodeStreamStateChanged(false) |
|||
return true |
|||
} |
|||
Log.d(TAG, "Opus数据流未在解码或OpusManager未初始化") |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* 处理接收到的音频数据进行解码 |
|||
* @param data 音频数据 (Opus编码) |
|||
*/ |
|||
fun processAudioData(data: ByteArray) { |
|||
if (!isInitialized) { |
|||
Log.w(TAG, "OpusAudioManager未初始化,忽略音频数据") |
|||
return |
|||
} |
|||
|
|||
if (opusManager?.isDecodeStream == true) { |
|||
// 将数据加入队列进行异步处理 |
|||
audioDataQueue.offer(data) |
|||
} else { |
|||
Log.d(TAG, "Opus解码流未启动,忽略音频数据") |
|||
} |
|||
} |
|||
|
|||
// ====================================================================================================== |
|||
// Opus编码相关方法 |
|||
// ====================================================================================================== |
|||
|
|||
/** |
|||
* 开始Opus数据流编码 |
|||
* @return 是否成功开始编码 |
|||
*/ |
|||
fun startEncodeStream(): Boolean { |
|||
if (!isInitialized || opusManager == null) { |
|||
Log.e(TAG, "OpusManager未初始化,无法开始编码") |
|||
return false |
|||
} |
|||
|
|||
// 如果已经在编码流,先停止 |
|||
if (opusManager?.isEncodeStream == true) { |
|||
Log.d(TAG, "Opus编码流已在运行") |
|||
return true |
|||
} |
|||
|
|||
try { |
|||
Log.d(TAG, "准备开始Opus数据流编码") |
|||
opusManager?.startEncodeStream(object : OnEncodeStreamCallback { |
|||
override fun onEncodeStream(data: ByteArray?) { |
|||
Log.d(TAG, "Opus编码回调收到数据,大小: ${data?.size ?: 0} bytes") |
|||
if (data != null) { |
|||
|
|||
// 编码完成的数据处理: |
|||
// 1. 通知回调 |
|||
callback?.onAudioDataEncoded(data) |
|||
|
|||
// // 2. 将编码后的数据加入发送队列进行分块发送 |
|||
// addAudioDataToSendQueue(data) |
|||
} else { |
|||
Log.w(TAG, "编码回调收到空数据") |
|||
} |
|||
} |
|||
|
|||
override fun onStart() { |
|||
Log.i(TAG, "Opus数据流编码已开始") |
|||
callback?.onEncodeStreamStateChanged(true) |
|||
} |
|||
|
|||
override fun onComplete(outPath: String?) { |
|||
Log.i(TAG, "Opus数据流编码完成: $outPath") |
|||
callback?.onEncodeStreamStateChanged(false) |
|||
} |
|||
|
|||
override fun onError(code: Int, message: String?) { |
|||
Log.e(TAG, "Opus数据流编码错误: [$code] $message") |
|||
callback?.onError("encode", code, message) |
|||
callback?.onEncodeStreamStateChanged(false) |
|||
} |
|||
}) |
|||
|
|||
return true |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "启动Opus编码流失败: ${e.message}", e) |
|||
callback?.onError("encode", -1, e.message) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 停止Opus数据流编码 |
|||
* @return 是否成功停止编码 |
|||
*/ |
|||
fun stopEncodeStream(): Boolean { |
|||
if (opusManager?.isEncodeStream == true) { |
|||
opusManager?.stopEncodeStream() |
|||
stopAudioSendThread() // 停止音频发送线程 |
|||
Log.i(TAG, "已停止Opus数据流编码") |
|||
callback?.onEncodeStreamStateChanged(false) |
|||
return true |
|||
} |
|||
Log.d(TAG, "Opus数据流未在编码或OpusManager未初始化") |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* 写入外部音频数据到Opus编码流 |
|||
* 该函数用于将外部音频数据(如麦克风录音)写入到Opus编码器中 |
|||
* |
|||
* @param data 音频数据字节数组,通常为PCM格式 |
|||
*/ |
|||
fun writeExternalAudioData(data: ByteArray) { |
|||
if (!isInitialized || opusManager == null) { |
|||
Log.e(TAG, "OpusManager未初始化") |
|||
return |
|||
} |
|||
|
|||
// 如果已经在编码流中,直接写入数据 |
|||
if (opusManager?.isEncodeStream == true) { |
|||
Log.d(TAG, "正在进行Opus编码流,写入音频数据") |
|||
// 将外部音频数据写入编码流 |
|||
opusManager?.writeEncodeStream(data) |
|||
} else { |
|||
Log.w(TAG, "Opus编码流未启动,无法写入音频数据") |
|||
} |
|||
} |
|||
|
|||
// ====================================================================================================== |
|||
// 状态查询方法 |
|||
// ====================================================================================================== |
|||
|
|||
/** |
|||
* 检查是否正在解码 |
|||
* @return 是否正在解码 |
|||
*/ |
|||
fun isDecoding(): Boolean { |
|||
return opusManager?.isDecodeStream == true |
|||
} |
|||
|
|||
/** |
|||
* 检查是否正在编码 |
|||
* @return 是否正在编码 |
|||
*/ |
|||
fun isEncoding(): Boolean { |
|||
return opusManager?.isEncodeStream == true |
|||
} |
|||
|
|||
/** |
|||
* 检查是否已初始化 |
|||
* @return 是否已初始化 |
|||
*/ |
|||
fun isInitialized(): Boolean { |
|||
return isInitialized |
|||
} |
|||
|
|||
// ====================================================================================================== |
|||
// 私有方法 - 音频数据处理 |
|||
// ====================================================================================================== |
|||
|
|||
|
|||
/** |
|||
* 停止音频发送线程 |
|||
*/ |
|||
private fun stopAudioSendThread() { |
|||
isAudioSending.set(false) |
|||
audioSendThread?.interrupt() |
|||
|
|||
try { |
|||
audioSendThread?.join(1000) // 等待最多1秒 |
|||
} catch (e: InterruptedException) { |
|||
Log.w(TAG, "等待音频发送线程停止时被中断") |
|||
} |
|||
|
|||
audioSendThread = null |
|||
audioSendQueue.clear() |
|||
|
|||
synchronized(audioBuffer) { |
|||
audioBuffer.clear() |
|||
} |
|||
|
|||
Log.d(TAG, "音频发送线程已停止") |
|||
} |
|||
|
|||
/** |
|||
* 启动音频队列处理线程 |
|||
*/ |
|||
private fun startAudioQueueProcessing() { |
|||
audioQueueProcessorThread = Thread { |
|||
Log.d(TAG, "音频队列处理线程已启动") |
|||
|
|||
while (!Thread.currentThread().isInterrupted) { |
|||
try { |
|||
// 从队列中取出音频数据进行处理 |
|||
val audioData = audioDataQueue.take() // 阻塞等待 |
|||
|
|||
// 将音频数据写入Opus解码流 |
|||
opusManager?.writeAudioStream(audioData) |
|||
|
|||
} catch (e: InterruptedException) { |
|||
Log.d(TAG, "音频队列处理线程被中断") |
|||
break |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "音频队列处理异常: ${e.message}", e) |
|||
} |
|||
} |
|||
|
|||
Log.d(TAG, "音频队列处理线程已停止") |
|||
} |
|||
|
|||
audioQueueProcessorThread?.start() |
|||
} |
|||
} |
|||
Loading…
Reference in new issue