fdp 1 year ago
parent
commit
fe54268857
  1. 180
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  2. 160
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/RecordingFile.kt

180
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -1575,43 +1575,11 @@ startBytesStatistics()
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) {
@ -1981,148 +1949,4 @@ startBytesStatistics()
}
class RecordingFile(private val context: Context) {
private var currentAudioFile: File? = null
private var fos: FileOutputStream? = null
// 新增:用于异步写入的队列和线程
private val writeQueue = LinkedBlockingQueue<ByteArray>()
private val isWriting = AtomicBoolean(false)
private var writeThread: Thread? = null
// 用于存储音频数据的缓冲区
private val dataBuffer = mutableListOf<ByteArray>()
private var totalBytesWritten = 0
var fileName = ""
internal fun creatingFiles() {
if (fos != null || currentAudioFile != null) {
return
}
// 创建新的音频文件
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
val timestamp = dateFormat.format(Date())
val filePath =
File(context.getExternalFilesDir(null), "${fileName}_${timestamp}.txt").absolutePath
currentAudioFile = File(filePath)
currentAudioFile?.createNewFile()
// 追加音频数据到文件
fos = FileOutputStream(currentAudioFile, true)
startWriteThread() // 新增:启动写入线程
Log.d("TAG", "创建文件: ${currentAudioFile?.absolutePath}")
}
// 新增:启动写入线程
private fun startWriteThread() {
if (isWriting.get()) return
isWriting.set(true)
writeThread = Thread {
try {
while (isWriting.get() || writeQueue.isNotEmpty()) {
val data = writeQueue.poll() ?: continue
fos?.write(data)
}
} catch (e: Exception) {
Log.e("", "异步写入音频数据失败: ${e.message}")
}
}
writeThread?.start()
}
/**
* 保存音频数据到 opus 文件(异步)
* @param buffer 音频数据字节数组
*/
internal fun saveAudioDataToWav(buffer: ByteArray) {
if (fos == null || currentAudioFile == null) return
// 放入队列,由写线程写入
// // 1. 将字节数据转换为十六进制字符串
// val hexData = buildString {
// buffer.forEachIndexed { index, byte ->
// append("%02X".format(byte))
// }
// }
// 2. 写入十六进制字符串
writeQueue.offer(buffer)
totalBytesWritten += buffer.size
}
/**
* 保存日志信息到文件(重载方法)
* 使用UTF-8编码处理字符串
* @param logMessage 日志消息字符串
*/
internal fun saveAudioDataToWav(logMessage: String) {
if (fos == null || currentAudioFile == null) return
try {
// 使用UTF-8编码将字符串转换为字节数组,并添加换行符
val logBytes = (logMessage + "\n").toByteArray(Charsets.UTF_8)
writeQueue.offer(logBytes)
totalBytesWritten += logBytes.size
Log.d("RecordingFile", "保存日志信息: ${logMessage.length} 字符, ${logBytes.size} 字节 (UTF-8)")
} catch (e: Exception) {
Log.e("RecordingFile", "UTF-8编码转换失败: ${e.message}")
}
}
/**
* 将音频数据转换为UTF-8编码的十六进制字符串格式并保存
* @param buffer 音频数据字节数组
* @param addTimestamp 是否添加时间戳,默认为true
*/
internal fun saveAudioDataAsUtf8Hex(buffer: ByteArray, addTimestamp: Boolean = true) {
if (fos == null || currentAudioFile == null) return
try {
val timestamp = if (addTimestamp) {
"[${SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())}] "
} else ""
// 将字节数组转换为十六进制字符串
val hexString = buffer.joinToString(" ") { "%02X".format(it) }
val dataLine = "${timestamp}音频数据 (${buffer.size} 字节): $hexString\n"
// 使用UTF-8编码转换为字节数组
val utf8Bytes = dataLine.toByteArray(Charsets.UTF_8)
writeQueue.offer(utf8Bytes)
totalBytesWritten += utf8Bytes.size
Log.d("RecordingFile", "保存UTF-8格式音频数据: ${buffer.size} 字节 -> ${utf8Bytes.size} UTF-8字节")
} catch (e: Exception) {
Log.e("RecordingFile", "UTF-8格式保存失败: ${e.message}")
}
}
internal fun closeFile() {
try {
if (fos == null || currentAudioFile == null) {
return
}
isWriting.set(false)
writeThread?.join(500) // 等待写线程结束
fos?.close()
// 更新WAV文件头
currentAudioFile?.let { file ->
// 如果文件小于44字节,直接删除
if (file.length() <= 0) {
file.delete()
Log.d("TAG", "音频文件过小已删除: ${file.absolutePath}")
} else {
Log.d("TAG", "音频文件保存完成: ${file.absolutePath}")
}
}
} catch (e: Exception) {
Log.e("TAG", "更新opus失败: ${e.message}")
} finally {
fos = null
currentAudioFile = null
}
}
}

160
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/RecordingFile.kt

@ -0,0 +1,160 @@
package com.yunqiinnovation.ble_service
import android.content.Context
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean
/**
* 录音文件管理类:负责音频数据的异步写入和文件管理
*/
class RecordingFile(private val context: Context) {
private var currentAudioFile: File? = null
private var fos: FileOutputStream? = null
// 新增:用于异步写入的队列和线程
private val writeQueue = LinkedBlockingQueue<ByteArray>()
private val isWriting = AtomicBoolean(false)
private var writeThread: Thread? = null
// 用于存储音频数据的缓冲区
private val dataBuffer = mutableListOf<ByteArray>()
private var totalBytesWritten = 0
var fileName = ""
internal fun creatingFiles() {
if (fos != null || currentAudioFile != null) {
return
}
// 创建新的音频文件
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
val timestamp = dateFormat.format(Date())
val filePath =
File(context.getExternalFilesDir(null), "${fileName}_${timestamp}.txt").absolutePath
currentAudioFile = File(filePath)
currentAudioFile?.createNewFile()
// 追加音频数据到文件
fos = FileOutputStream(currentAudioFile, true)
startWriteThread() // 新增:启动写入线程
Log.d("TAG", "创建文件: ${currentAudioFile?.absolutePath}")
}
// 新增:启动写入线程
private fun startWriteThread() {
if (isWriting.get()) return
isWriting.set(true)
writeThread = Thread {
try {
while (isWriting.get() || writeQueue.isNotEmpty()) {
val data = writeQueue.poll() ?: continue
fos?.write(data)
}
} catch (e: Exception) {
Log.e("", "异步写入音频数据失败: ${e.message}")
}
}
writeThread?.start()
}
/**
* 保存音频数据到 opus 文件(异步)
* @param buffer 音频数据字节数组
*/
internal fun saveAudioDataToWav(buffer: ByteArray) {
if (fos == null || currentAudioFile == null) return
// 放入队列,由写线程写入
// // 1. 将字节数据转换为十六进制字符串
// val hexData = buildString {
// buffer.forEachIndexed { index, byte ->
// append("%02X".format(byte))
// }
// }
// 2. 写入十六进制字符串
writeQueue.offer(buffer)
totalBytesWritten += buffer.size
}
/**
* 保存日志信息到文件(重载方法)
* 使用UTF-8编码处理字符串
* @param logMessage 日志消息字符串
*/
internal fun saveAudioDataToWav(logMessage: String) {
if (fos == null || currentAudioFile == null) return
try {
// 使用UTF-8编码将字符串转换为字节数组,并添加换行符
val logBytes = (logMessage + "\n").toByteArray(Charsets.UTF_8)
writeQueue.offer(logBytes)
totalBytesWritten += logBytes.size
Log.d("RecordingFile", "保存日志信息: ${logMessage.length} 字符, ${logBytes.size} 字节 (UTF-8)")
} catch (e: Exception) {
Log.e("RecordingFile", "UTF-8编码转换失败: ${e.message}")
}
}
/**
* 将音频数据转换为UTF-8编码的十六进制字符串格式并保存
* @param buffer 音频数据字节数组
* @param addTimestamp 是否添加时间戳,默认为true
*/
internal fun saveAudioDataAsUtf8Hex(buffer: ByteArray, addTimestamp: Boolean = true) {
if (fos == null || currentAudioFile == null) return
try {
val timestamp = if (addTimestamp) {
"[${SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())}] "
} else ""
// 将字节数组转换为十六进制字符串
val hexString = buffer.joinToString(" ") { "%02X".format(it) }
val dataLine = "${timestamp}音频数据 (${buffer.size} 字节): $hexString\n"
// 使用UTF-8编码转换为字节数组
val utf8Bytes = dataLine.toByteArray(Charsets.UTF_8)
writeQueue.offer(utf8Bytes)
totalBytesWritten += utf8Bytes.size
Log.d("RecordingFile", "保存UTF-8格式音频数据: ${buffer.size} 字节 -> ${utf8Bytes.size} UTF-8字节")
} catch (e: Exception) {
Log.e("RecordingFile", "UTF-8格式保存失败: ${e.message}")
}
}
internal fun closeFile() {
try {
if (fos == null || currentAudioFile == null) {
return
}
isWriting.set(false)
writeThread?.join(500) // 等待写线程结束
fos?.close()
// 更新WAV文件头
currentAudioFile?.let { file ->
// 如果文件小于44字节,直接删除
if (file.length() <= 0) {
file.delete()
Log.d("TAG", "音频文件过小已删除: ${file.absolutePath}")
} else {
Log.d("TAG", "音频文件保存完成: ${file.absolutePath}")
}
}
} catch (e: Exception) {
Log.e("TAG", "更新opus失败: ${e.message}")
} finally {
fos = null
currentAudioFile = null
}
}
}
Loading…
Cancel
Save