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