2 changed files with 162 additions and 178 deletions
@ -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…
Reference in new issue