From 2671f5d137692de76449fda1a32ad0ef2694a3fe Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Fri, 19 Sep 2025 16:55:14 +0800 Subject: [PATCH] =?UTF-8?q?opus=E6=97=A5=E8=AE=B0=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/opus_test_controller.dart | 2 +- lib/modules/settings/views/settings_view.dart | 44 +++++----- .../yunqiinnovation/ble_service/BleService.kt | 80 ++++++++++++++----- 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/lib/modules/opus_test/controllers/opus_test_controller.dart b/lib/modules/opus_test/controllers/opus_test_controller.dart index 81ee98093..b22cc4b5e 100644 --- a/lib/modules/opus_test/controllers/opus_test_controller.dart +++ b/lib/modules/opus_test/controllers/opus_test_controller.dart @@ -140,7 +140,7 @@ class OpusTestController extends GetxController { for (var entity in files) { if (entity is File) { final path = entity.path.toLowerCase(); - if (path.endsWith('.opus')) { + if (path.endsWith('.opus') || path.endsWith('.txt')) { final fileSize = await entity.length(); externalFiles.add(PlatformFile( name: entity.path.split('/').last, diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 42b9daa3d..a5d7149e8 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -514,28 +514,28 @@ class SettingsView extends GetView { // color: isDarkMode // ? Colors.white.withOpacity(0.1) // : Colors.grey[200]), - // //BLE测试 - // _buildSimpleNavigationSetting( - // title: 'opus解码测试', - // subtitle: '测试opus解码', - // icon: Icons.bluetooth_searching, - // iconBgColor: isDarkMode - // ? Colors.green[900]!.withOpacity(0.3) - // : Colors.green[100]!, - // iconColor: - // isDarkMode ? Colors.green[300]! : Colors.green[600]!, - // titleColor: isDarkMode ? Colors.white : null, - // subtitleColor: isDarkMode ? Colors.white70 : null, - // onTap: () { - // Get.toNamed(Routes.opusTest); - // }, - // isDarkMode: isDarkMode, - // ), - // Divider( - // height: 1, - // color: isDarkMode - // ? Colors.white.withOpacity(0.1) - // : Colors.grey[200]), + //BLE测试 + _buildSimpleNavigationSetting( + title: 'opus解码测试', + subtitle: '测试opus解码', + icon: Icons.bluetooth_searching, + iconBgColor: isDarkMode + ? Colors.green[900]!.withOpacity(0.3) + : Colors.green[100]!, + iconColor: + isDarkMode ? Colors.green[300]! : Colors.green[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.opusTest); + }, + isDarkMode: isDarkMode, + ), + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), // BLE测试 // _buildSimpleNavigationSetting( diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 87355f98c..375807025 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -1360,7 +1360,7 @@ startBytesStatistics() if (data != null) { // 编码完成的数据处理: // 1. 保存到WAV文件 - recordfile1?.saveAudioDataToWav(data) + // 2. 将编码后的数据加入发送队列进行分块发送 addAudioDataToSendQueue(data) } else { @@ -1493,13 +1493,16 @@ startBytesStatistics() audioSendHandler.post { try { callWriteChar?.value = chunk + recordfile1?.saveAudioDataAsUtf8Hex(chunk, true) val isSuccess = bluetoothGatt?.writeCharacteristic(callWriteChar) if (isSuccess == true) { + recordfile1?.saveAudioDataToWav("发送成功,大小: ${chunk.size} 字节") // 累加发送成功的字节数 bytesSentInCurrentSecond += chunk.size Log.d(TAG, "成功发送音频数据块,大小: ${chunk.size} 字节") } else { + recordfile1?.saveAudioDataToWav("发送失败,大小: ${chunk.size} 字节") Log.e(TAG, "发送音频数据块失败,大小: ${chunk.size} 字节") // 详细的失败原因分析 val failureReason = StringBuilder("发送音频数据块失败,大小: ${chunk.size} 字节") @@ -1636,7 +1639,6 @@ startBytesStatistics() */ fun closeCodec(): Boolean { Log.i(TAG, "关闭编解码...") - return sendCommand( BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( @@ -1646,12 +1648,7 @@ startBytesStatistics() ) // 或其他默认声道 ) } - fun closeCodec1(): Boolean { - recordfile1!!.closeFile() - - recordfile!!.closeFile() - return true - } + /** * 控制编解码 - 打开解码 */ @@ -1682,6 +1679,7 @@ startBytesStatistics() */ fun openA2DPDecoder(): Boolean { Log.i(TAG, "打开编码 0xA2") + startOpusEncodeStream() //双声道 80字节 startOpusStreamDecoding(false, 2, 16000, 80) @@ -1724,10 +1722,7 @@ startBytesStatistics() */ fun openEncoder(): Boolean { Log.i(TAG, "打开编码0xB1") - recordfile1!!.closeFile() - recordfile1!!.creatingFiles() - recordfile!!.closeFile() - recordfile!!.creatingFiles() + startOpusStreamDecoding(false, 1, 16000, 40) // // 目前仅发送命令通知设备开始编码。 // return sendCommand( @@ -2096,7 +2091,7 @@ class RecordingFile(private val context: Context) { val timestamp = dateFormat.format(Date()) val filePath = - File(context.getExternalFilesDir(null), "${fileName}_${timestamp}.opus").absolutePath + File(context.getExternalFilesDir(null), "${fileName}_${timestamp}.txt").absolutePath currentAudioFile = File(filePath) currentAudioFile?.createNewFile() // 追加音频数据到文件 @@ -2124,17 +2119,18 @@ class RecordingFile(private val context: Context) { /** * 保存音频数据到 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)) + // // 1. 将字节数据转换为十六进制字符串 + // val hexData = buildString { + // buffer.forEachIndexed { index, byte -> + // append("%02X".format(byte)) - // } - // } + // } + // } // 2. 写入十六进制字符串 @@ -2142,6 +2138,52 @@ class RecordingFile(private val context: Context) { 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 {