Browse Source

add

newdev_shunjiawei
fdp 1 year ago
parent
commit
21b7077a4f
  1. 21
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  2. 53
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  3. 191
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/RecordFile.kt
  4. 4
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  5. 51
      local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt

21
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -584,17 +584,18 @@ class AzureAsrHelper(private val context: Context) {
recordfile!!.closeFile(true) recordfile!!.closeFile(true)
recordfile!!.creatingFiles(filePath) recordfile!!.creatingFiles(filePath)
} }
/**
/**
* 移动文件到新路径 * 移动文件到新路径
*/ */
fun moveFile(sourcePath: String, destPath: String): Boolean { fun moveFile(sourcePath: String, destPath: String): Boolean {
Log.i(tag, "移动文件到新路径:") Log.i(tag, "移动文件到新路径:")
recordfile!!.moveFile(sourcePath,destPath) recordfile!!.moveFile(sourcePath, destPath)
return true return true
} }
@ -605,27 +606,27 @@ class AzureAsrHelper(private val context: Context) {
Log.i(tag, "重命名指定路径的音频文件:") Log.i(tag, "重命名指定路径的音频文件:")
recordfile!!.renameFile(filePath,newName) recordfile!!.renameFile(filePath, newName)
return true return true
} }
/** /**
* 停止录音 * 停止录音
*/ */
fun pauseRecord() { fun pauseRecord() {
Log.i(tag, "停止连续录音:") Log.i(tag, "停止连续录音:")
recordfile!!.isPause=true recordfile!!.isPause = true
} }
/** /**
* 关闭录音 * 关闭录音
*/ */
fun stopRecord(isSave:Boolean) { fun stopRecord(isSave: Boolean) {
Log.i(tag, "关闭录音:") Log.i(tag, "关闭录音:")
recordfile!!.isPause=false recordfile!!.isPause = false
recordfile!!.closeFile(isSave) recordfile!!.closeFile(isSave)
} }

53
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -11,7 +11,6 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel
import com.microsoft.cognitiveservices.speech.audio.*
import com.deep_voice.speech.tts.TtsEvent import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType import com.deep_voice.speech.tts.TtsEventType
@ -356,49 +355,53 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"renameFile" -> { "renameFile" -> {
val filePath = call.argument<String>("filePath")?: "" val filePath = call.argument<String>("filePath") ?: ""
val newName = call.argument<String>("newName")?: "" val newName = call.argument<String>("newName") ?: ""
try { try {
FileLogger.d(tag, "音频文件名称为: ${filePath}") // FileLogger.d(tag, "音频文件名称为: ${filePath}") //
azureAsrHelper.renameFile(filePath,newName) azureAsrHelper.renameFile(filePath, newName)
result.success(true) result.success(true)
} catch (e: Exception) { } catch (e: Exception) {
result.error("ENABLERECORD_ERROR", e.message, null) result.error("ENABLERECORD_ERROR", e.message, null)
} }
} }
"moveFile" -> {
val sourcePath = call.argument<String>("sourcePath")?: "" "moveFile" -> {
val destPath = call.argument<String>("destPath")?: "" val sourcePath = call.argument<String>("sourcePath") ?: ""
val destPath = call.argument<String>("destPath") ?: ""
try { try {
FileLogger.d(tag, "音频文件名称为: ${sourcePath}") // FileLogger.d(tag, "音频文件名称为: ${sourcePath}") //
azureAsrHelper.moveFile(sourcePath,destPath) azureAsrHelper.moveFile(sourcePath, destPath)
result.success(true) result.success(true)
} catch (e: Exception) { } catch (e: Exception) {
result.error("ENABLERECORD_ERROR", e.message, null) result.error("ENABLERECORD_ERROR", e.message, null)
} }
} }
"enableRecord" -> {
val filePath = call.argument<String>("filePath")?: "" "enableRecord" -> {
val filePath = call.argument<String>("filePath") ?: ""
try { try {
FileLogger.d(tag, "音频文件名称为: ${filePath}") // FileLogger.d(tag, "音频文件名称为: ${filePath}") //
azureAsrHelper.enableRecord(filePath) azureAsrHelper.enableRecord(filePath)
result.success(true) result.success(true)
} catch (e: Exception) { } catch (e: Exception) {
result.error("ENABLERECORD_ERROR", e.message, null) result.error("ENABLERECORD_ERROR", e.message, null)
} }
} }
"pauseRecord" -> {
"pauseRecord" -> {
azureAsrHelper.pauseRecord() azureAsrHelper.pauseRecord()
result.success(true) result.success(true)
} }
"stopRecord" -> { "stopRecord" -> {
val isSave = call.argument<Boolean>("isSave")?:false val isSave = call.argument<Boolean>("isSave") ?: false
try { try {
azureAsrHelper.stopRecord(isSave) azureAsrHelper.stopRecord(isSave)
result.success(true) result.success(true)
} catch (e: Exception) { } catch (e: Exception) {
result.error("ENABLERECORD_ERROR", e.message, null) result.error("ENABLERECORD_ERROR", e.message, null)
} }
} }

191
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/RecordFile.kt

@ -1,4 +1,5 @@
package com.yunqiinnovation.azure_speech.tools package com.yunqiinnovation.azure_speech.tools
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
@ -8,30 +9,30 @@ import java.io.FileInputStream
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
object RecordFile { object RecordFile {
private var currentAudioFile: File? = null private var currentAudioFile: File? = null
private var fos: FileOutputStream? = null private var fos: FileOutputStream? = null
// 新增:用于异步写入的队列和线程 // 新增:用于异步写入的队列和线程
private val writeQueue = LinkedBlockingQueue<ByteArray>() private val writeQueue = LinkedBlockingQueue<ByteArray>()
private val isWriting = AtomicBoolean(false) private val isWriting = AtomicBoolean(false)
private var writeThread: Thread? = null private var writeThread: Thread? = null
// 用于存储音频数据的缓冲区 // 用于存储音频数据的缓冲区
private val dataBuffer = mutableListOf<ByteArray>() private val dataBuffer = mutableListOf<ByteArray>()
private var totalBytesWritten = 0 private var totalBytesWritten = 0
// 音频配置 // 音频配置
private val sampleRate = 16000 private val sampleRate = 16000
// 新增:用于存储最后成功保存的文件 // 新增:用于存储最后成功保存的文件
private var lastSavedFile: File? = null private var lastSavedFile: File? = null
private var lastFileTimestamp: String = "" private var lastFileTimestamp: String = ""
var isPause = false var isPause = false
var fileName = "" var fileName = ""
// 修改:添加 filePath 参数 // 修改:添加 filePath 参数
internal fun creatingFiles(filePath: String) { internal fun creatingFiles(filePath: String) {
if (fos != null || currentAudioFile != null) { if (fos != null || currentAudioFile != null) {
return return
@ -56,59 +57,59 @@ import java.util.concurrent.atomic.AtomicBoolean
} }
// 新增:启动写入线程 // 新增:启动写入线程
private fun startWriteThread() { private fun startWriteThread() {
if (isWriting.get()) return if (isWriting.get()) return
isWriting.set(true) isWriting.set(true)
writeThread = Thread { writeThread = Thread {
try { try {
while (isWriting.get() || writeQueue.isNotEmpty()) { while (isWriting.get() || writeQueue.isNotEmpty()) {
val data = writeQueue.poll() ?: continue val data = writeQueue.poll() ?: continue
fos?.write(data) fos?.write(data)
}
} catch (e: Exception) {
Log.e("tag", "异步写入音频数据失败: ${e.message}")
} }
} catch (e: Exception) {
Log.e("tag", "异步写入音频数据失败: ${e.message}")
} }
writeThread?.start()
} }
writeThread?.start()
}
/** /**
* 保存音频数据到 WAV 文件(异步) * 保存音频数据到 WAV 文件(异步)
*/ */
internal fun saveAudioDataToWav(buffer: ByteArray) { internal fun saveAudioDataToWav(buffer: ByteArray) {
if (fos == null || currentAudioFile == null) return if (fos == null || currentAudioFile == null) return
// 放入队列,由写线程写入 // 放入队列,由写线程写入
writeQueue.offer(buffer.copyOf()) writeQueue.offer(buffer.copyOf())
totalBytesWritten += buffer.size totalBytesWritten += buffer.size
} }
// 更新文件头生成(修正RIFF长度计算) // 更新文件头生成(修正RIFF长度计算)
private fun generateWavHeader(dataLength: Int): ByteArray { private fun generateWavHeader(dataLength: Int): ByteArray {
val totalLength = 36 + dataLength // RIFF块总长度 = 头部36字节 + 音频数据 val totalLength = 36 + dataLength // RIFF块总长度 = 头部36字节 + 音频数据
val byteRate = sampleRate * 2 * 1 // 采样率 * 字节/样本 * 通道数 val byteRate = sampleRate * 2 * 1 // 采样率 * 字节/样本 * 通道数
return byteArrayOf( return byteArrayOf(
'R'.code.toByte(), 'I'.code.toByte(), 'F'.code.toByte(), 'F'.code.toByte(), 'R'.code.toByte(), 'I'.code.toByte(), 'F'.code.toByte(), 'F'.code.toByte(),
(totalLength and 0xFF).toByte(), ((totalLength shr 8) and 0xFF).toByte(), (totalLength and 0xFF).toByte(), ((totalLength shr 8) and 0xFF).toByte(),
((totalLength shr 16) and 0xFF).toByte(), ((totalLength shr 24) and 0xFF).toByte(), ((totalLength shr 16) and 0xFF).toByte(), ((totalLength shr 24) and 0xFF).toByte(),
'W'.code.toByte(), 'A'.code.toByte(), 'V'.code.toByte(), 'E'.code.toByte(), 'W'.code.toByte(), 'A'.code.toByte(), 'V'.code.toByte(), 'E'.code.toByte(),
'f'.code.toByte(), 'm'.code.toByte(), 't'.code.toByte(), ' '.code.toByte(), 'f'.code.toByte(), 'm'.code.toByte(), 't'.code.toByte(), ' '.code.toByte(),
16, 0, 0, 0, // PCM头长度 16, 0, 0, 0, // PCM头长度
1, 0, // PCM格式 1, 0, // PCM格式
1, 0, // 单声道 1, 0, // 单声道
(sampleRate and 0xFF).toByte(), ((sampleRate shr 8) and 0xFF).toByte(), 0, 0, // 采样率 (sampleRate and 0xFF).toByte(), ((sampleRate shr 8) and 0xFF).toByte(), 0, 0, // 采样率
(byteRate and 0xFF).toByte(), ((byteRate shr 8) and 0xFF).toByte(), 0, 0, // 字节率 (byteRate and 0xFF).toByte(), ((byteRate shr 8) and 0xFF).toByte(), 0, 0, // 字节率
2, 0, // 块对齐 (通道数 * 样本位数/8) 2, 0, // 块对齐 (通道数 * 样本位数/8)
16, 0, // 样本位数 16, 0, // 样本位数
'd'.code.toByte(), 'a'.code.toByte(), 't'.code.toByte(), 'a'.code.toByte(), 'd'.code.toByte(), 'a'.code.toByte(), 't'.code.toByte(), 'a'.code.toByte(),
(dataLength and 0xFF).toByte(), ((dataLength shr 8) and 0xFF).toByte(), (dataLength and 0xFF).toByte(), ((dataLength shr 8) and 0xFF).toByte(),
((dataLength shr 16) and 0xFF).toByte(), ((dataLength shr 24) and 0xFF).toByte() ((dataLength shr 16) and 0xFF).toByte(), ((dataLength shr 24) and 0xFF).toByte()
) )
} }
/** /**
* 重命名指定路径的音频文件 * 重命名指定路径的音频文件
* @param filePath 要重命名的文件完整路径 * @param filePath 要重命名的文件完整路径
* @param newName 新的文件名(不含扩展名) * @param newName 新的文件名(不含扩展名)
@ -156,7 +157,8 @@ import java.util.concurrent.atomic.AtomicBoolean
false false
} }
} }
/**
/**
* 移动文件到新路径 * 移动文件到新路径
* @param sourcePath 原始文件完整路径 * @param sourcePath 原始文件完整路径
* @param destPath 目标文件完整路径 * @param destPath 目标文件完整路径
@ -213,39 +215,40 @@ import java.util.concurrent.atomic.AtomicBoolean
false false
} }
} }
internal fun closeFile( isSave:Boolean): Boolean {
try { internal fun closeFile(isSave: Boolean): Boolean {
if (fos == null || currentAudioFile == null) { try {
if (fos == null || currentAudioFile == null) {
return false
}
isWriting.set(false)
writeThread?.join(500)
fos?.close()
currentAudioFile?.let { file ->
if (file.length() <= 44 || isSave == false) {
file.delete()
Log.d("tag", "音频文件过小已删除: ${file.absolutePath}")
return false return false
} } else {
isWriting.set(false) RandomAccessFile(file, "rw").use { raf ->
writeThread?.join(500) raf.seek(0)
fos?.close() raf.write(generateWavHeader(totalBytesWritten))
currentAudioFile?.let { file ->
if (file.length() <= 44||isSave==false) {
file.delete()
Log.d("tag", "音频文件过小已删除: ${file.absolutePath}")
return false
} else {
RandomAccessFile(file, "rw").use { raf ->
raf.seek(0)
raf.write(generateWavHeader(totalBytesWritten))
}
Log.d("tag", "音频文件保存完成: ${file.absolutePath}")
// 保存成功文件引用
lastSavedFile = file
return true
} }
Log.d("tag", "音频文件保存完成: ${file.absolutePath}")
// 保存成功文件引用
lastSavedFile = file
return true
} }
} catch (e: Exception) {
Log.e("tag", "更新WAV文件头失败: ${e.message}")
return false
} finally {
fos = null
currentAudioFile = null
} }
} catch (e: Exception) {
Log.e("tag", "更新WAV文件头失败: ${e.message}")
return false return false
} finally {
fos = null
currentAudioFile = null
} }
return false
} }
}

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

@ -16,14 +16,10 @@ import com.jieli.jl_audio_decode.exceptions.OpusException
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.RandomAccessFile
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import android.os.Handler import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import android.util.Log import android.util.Log
/** /**

51
local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt

@ -273,32 +273,31 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
} }
override fun onMethodCall(call: MethodCall, result: Result) { override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "init") { if (call.method == "init") {
// 初始化 otaManager 和 bluetoothHelper // 初始化 otaManager 和 bluetoothHelper
ConfigHelper.initialize(context) ConfigHelper.initialize(context)
otaManager = OTAManager(context) otaManager = OTAManager(context)
bluetoothHelper = BluetoothHelper(context) bluetoothHelper = BluetoothHelper(context)
// 如果需要额外的初始化逻辑,请在这里添加 // 如果需要额外的初始化逻辑,请在这里添加
otaManager.registerBluetoothCallback(bluetoothCallback) otaManager.registerBluetoothCallback(bluetoothCallback)
// 初始化BleService // 初始化BleService
// 2. 初始化BleService并注册回调 // 2. 初始化BleService并注册回调
if (BleService.initialize(context)) { if (BleService.initialize(context)) {
if (BleService.addCallback(this)) { if (BleService.addCallback(this)) {
isRegisteredToBle = true isRegisteredToBle = true
Log.d(TAG, "已注册BLE回调") Log.d(TAG, "已注册BLE回调")
} else {
Log.w(TAG, "BLE回调注册失败")
}
} else { } else {
Log.w(TAG, "BLE回调注册失败") Log.e(TAG, "BLE服务初始化失败")
}
} else {
Log.e(TAG, "BLE服务初始化失败")
} }
Log.i("TAG", "onAttachedToEngine") Log.i("TAG", "onAttachedToEngine")
result.success("") result.success("")
} } else if (call.method == "startUpgrading") {
else if (call.method == "startUpgrading") {
Log.d("TAG", "startUpgrading") Log.d("TAG", "startUpgrading")
// 接收传入的参数 // 接收传入的参数
val arguments = call.arguments as? Map<String, Any> val arguments = call.arguments as? Map<String, Any>
@ -331,16 +330,16 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
BleService.isEnterOta = false BleService.isEnterOta = false
} }
result.success("") result.success("")
} else if (call.method == "onOtaClose") { } else if (call.method == "onOtaClose") {
BleService.isEnterOta = false BleService.isEnterOta = false
isDisconnect = false isDisconnect = false
otaManager?.bluetoothHelper?.stopScan() otaManager?.bluetoothHelper?.stopScan()
otaManager?.bluetoothHelper?.disconnectDevice(otaDevice) otaManager?.bluetoothHelper?.disconnectDevice(otaDevice)
result.success("") result.success("")
} else { } else {
result.notImplemented() result.notImplemented()
} }

Loading…
Cancel
Save