From 06effa87026c429f480b2e45c31b7d551b947172 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Wed, 23 Apr 2025 10:27:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- local_plugins/jieli/android/build.gradle.kts | 2 + .../com/yunqiinnovation/jieli/BleService.kt | 53 ++++++---- .../com/yunqiinnovation/jieli/JieliPlugin.kt | 2 + .../yunqiinnovation/jl_opus/JlOpusPlugin.kt | 98 ++++++++++++++++++- local_plugins/jl_opus/lib/jl_opus.dart | 65 ++++++------ 5 files changed, 169 insertions(+), 51 deletions(-) diff --git a/local_plugins/jieli/android/build.gradle.kts b/local_plugins/jieli/android/build.gradle.kts index de08666ad..9602cb27e 100644 --- a/local_plugins/jieli/android/build.gradle.kts +++ b/local_plugins/jieli/android/build.gradle.kts @@ -53,4 +53,6 @@ dependencies { implementation("androidx.lifecycle:lifecycle-service:2.4.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2") implementation(project(":azure_speech")) + implementation(project(":jl_opus")) + implementation(project(":jl_opus")) } \ No newline at end of file diff --git a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt b/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt index 445f78a4a..1667021b6 100644 --- a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt +++ b/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt @@ -16,6 +16,8 @@ import java.text.SimpleDateFormat import java.util.* import com.yunqiinnovation.azure_speech.AzureAsrHelper import com.yunqiinnovation.azure_speech.AzureTtsHelper +import com.yunqiinnovation.jl_opus.JlOpusPlugin + /** * BLE服务类,提供核心蓝牙功能 */ @@ -65,6 +67,9 @@ class BleService(private val context: Context) { // Azure服务 private var azureAsrHelper: AzureAsrHelper? = null private var azureTtsHelper: AzureTtsHelper? = null + + // 获取插件实例 + private val opusPlugin = JlOpusPlugin() // 蓝牙管理器和适配器 private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager @@ -158,10 +163,10 @@ class BleService(private val context: Context) { val deviceAddress = device.address val rssi = result.rssi val scanRecord = result.scanRecord - // if (!deviceName.contains("sanag") && !deviceName.contains("Ola Friend")) { - // return - // } - //Log.d(TAG, "============发现设备: $deviceName [$deviceAddress], RSSI: $rssi") + if (!deviceName.contains("zxd")) { + return + } + Log.d(TAG, "============发现设备: $deviceName [$deviceAddress], RSSI: $rssi") // 打印所有发现的UUID以便调试 val serviceUuids = scanRecord?.serviceUuids @@ -175,20 +180,20 @@ class BleService(private val context: Context) { for (i in 0 until 65535) { val manufacturerData = scanRecord?.getManufacturerSpecificData(i) if (manufacturerData != null) { - // Log.d(TAG, "发现厂商ID: $i, 数据长度: ${manufacturerData.size}") + // Log.d(TAG, "发现厂商ID: $i, 数据长度: ${manufacturerData.size}") // 这里可以根据已知的厂商ID和格式解析数据 } } // 避免重复添加 - + val existingIndex = scanResults.indexOfFirst { it.device.address == deviceAddress } - + if (existingIndex >= 0) { - + scanResults[existingIndex] = result } else { - Log.d(TAG, "扫描回调A, scanResults[existingIndex]ADD") + Log.d(TAG, "扫描回调A, scanResults[existingIndex]ADD") scanResults.add(result) } @@ -333,6 +338,7 @@ class BleService(private val context: Context) { updateConnectionState(STATE_ERROR) } } + // 添加扩展函数(在文件顶部声明) fun List.toByteArray(): ByteArray { return ByteArray(this.size) { this[it] } @@ -345,6 +351,12 @@ class BleService(private val context: Context) { val characteristicUuid = characteristic.uuid.toString() Log.d(TAG, "收到特征变化通知: $characteristicUuid") + opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object : + JlOpusPlugin.Callback1 { + override fun onAudioDataReceived1(pcmData: ByteArray) { + Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}") + // 处理解码后的 PCM 数据(如播放或写入文件) + }}) when (characteristicUuid) { // NOTIFY_CHAR_UUID -> { // // 收到音频数据 @@ -362,11 +374,12 @@ class BleService(private val context: Context) { if (value.isNotEmpty() && (value[0].toInt() and 0xFF == 0x4b || value[0].toInt() and 0xFF == 0x48)) { // 处理设备返回的协议数据 fos?.write(value) - // 方法1:手动转换 -val byteArray = ByteArray(value.size) { index -> value[index] } - -// 或者直接使用现有 - azureAsrHelper?.pushAudioData(byteArray) + + + + opusPlugin?.handleDecodeRequest(value) + + //callback?.onAudioDataReceived(value) } // 判断是否是协议格式响应 if (value.isNotEmpty() && value[0].toInt() and 0xFF == 0xA5) { @@ -508,7 +521,8 @@ val byteArray = ByteArray(value.size) { index -> value[index] } writeChar?.value = commandData // 获取外部存储目录路径 // 生成带时间戳的文件名(格式:yyyyMMdd_HHmmss_audio.opus) - val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val timeStamp = + SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val fileName = "${timeStamp}_audio.opus" val externalDir = context.getExternalFilesDir(null) outputFile = File(externalDir, fileName) @@ -519,10 +533,10 @@ val byteArray = ByteArray(value.size) { index -> value[index] } // val subscriptionKey = call.argument("subscriptionKey") ?: "" // val region = call.argument("region") ?: "" // val supportedLanguages = call.argument>("supportedLanguages") ?: listOf("zh-CN") - + // val success = azureAsrHelper.initialize(subscriptionKey, region, supportedLanguages.toTypedArray()) // result.success(success) - + val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false @@ -599,7 +613,8 @@ val byteArray = ByteArray(value.size) { index -> value[index] } (volumeData.toInt() and 0xFF)) and 0xFF // 创建完整命令 - val commandData = byteArrayOf(header, fieldType, dataLen, volumeData, checksum.toByte()) + val commandData = + byteArrayOf(header, fieldType, dataLen, volumeData, checksum.toByte()) // 写入特征 writeChar?.value = commandData @@ -646,4 +661,4 @@ val byteArray = ByteArray(value.size) { index -> value[index] } notifyChar = null writeChar = null } -} \ No newline at end of file +} \ No newline at end of file diff --git a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt b/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt index 95a267e97..e2f474564 100644 --- a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt +++ b/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt @@ -6,6 +6,7 @@ import android.bluetooth.BluetoothManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.util.Log import androidx.annotation.NonNull import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat @@ -91,6 +92,7 @@ class JieliPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel override fun onAudioDataReceived(data: ByteArray) { pluginScope.launch(Dispatchers.Main) { + Log.d("","数据流onAudioDataReceived${data}") sendEvent(EVENT_TYPE_AUDIO_DATA, data) } } diff --git a/local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt b/local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt index f6bfa8f29..3d6a5ef75 100644 --- a/local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt +++ b/local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt @@ -25,22 +25,35 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler { private lateinit var context: Context private var opusManager: OpusManager? = null private var streamSink: EventChannel.EventSink? = null - + // 修正1:正确的主构造函数写法 + constructor() { + opusManager = OpusManager() + Log.d(TAG, "OpusManager初始化成功") + } + // 添加主线程Handler用于确保在主线程回调 private val mainHandler = Handler(Looper.getMainLooper()) - + interface Callback1 { + + fun onAudioDataReceived1(data: ByteArray) + + } + companion object { private const val TAG = "JlOpusPlugin" private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus" private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events" + } override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + context = flutterPluginBinding.applicationContext channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME) channel.setMethodCallHandler(this) eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME) + eventChannel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { streamSink = events @@ -177,7 +190,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler { val sampleRate = call.argument("sampleRate") ?: 16000 val packetSize = call.argument("packetSize") ?: 40 - // 如果已经在解码流,先停止 + // 如果已经在解码流,先停止 if (opusManager?.isDecodeStream == true) { opusManager?.stopDecodeStream() } @@ -270,8 +283,87 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler { } } } + fun handleDecodeRequest(opusdata: ByteArray) { + + Log.d(TAG, "handleDecodeRequest${opusdata}") + if (opusdata == null) { + + return + } + if (opusManager?.isDecodeStream == true) { + opusManager?.writeAudioStream(opusdata) + + } else { + + } +} + fun startOpusDecodeStream( + hasHeader: Boolean, + channel: Int, + sampleRate: Int, + packetSize: Int, + callback: Callback1 + +) { + Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager") + // 如果已经在解码流,先停止 + if (opusManager?.isDecodeStream == true) { + opusManager?.stopDecodeStream() + } + + // 创建解码参数 + val option = OpusOption() + .setHasHead(hasHeader) + .setChannel(channel) + .setSampleRate(sampleRate) + .setPacketSize(packetSize) + + Log.d(TAG, "开始解码数据流, 参数: $option") + + opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { + override fun onDecodeStream(data: ByteArray?) { + if (data != null) { + Log.d(TAG, "onDecodeStream${data}") + callback.onAudioDataReceived1(data) + sendEventOnMainThread(mapOf( + "event" to "onDecodeStream", + "data" to data + )) + } + } + + override fun onStart() { + Log.d(TAG, "数据流解码开始") + sendEventOnMainThread(mapOf( + "event" to "onStart", + "type" to "stream" + )) + + } + + override fun onComplete(outPath: String?) { + Log.d(TAG, "数据流解码完成: $outPath") + sendEventOnMainThread(mapOf( + "event" to "onComplete", + "type" to "stream" + )) + } + + override fun onError(code: Int, message: String?) { + Log.e(TAG, "数据流解码错误: [$code] $message") + sendEventOnMainThread(mapOf( + "event" to "onError", + "type" to "stream", + "code" to code, + "message" to message + )) + + } + }) +} override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) eventChannel.setStreamHandler(null) opusManager?.let { diff --git a/local_plugins/jl_opus/lib/jl_opus.dart b/local_plugins/jl_opus/lib/jl_opus.dart index dc63a0cd3..caa927af9 100644 --- a/local_plugins/jl_opus/lib/jl_opus.dart +++ b/local_plugins/jl_opus/lib/jl_opus.dart @@ -6,13 +6,13 @@ import 'package:flutter/services.dart'; class OpusOption { /// 是否具有协议头 final bool hasHeader; - + /// 通道数。取值范围:[1, 2] final int channel; - + /// 采样率。参考值:{8000, 16000, 24000, 32000, 44100, 48000} final int sampleRate; - + /// 数据包长度。仅hasHeader = false时生效。 final int packetSize; @@ -39,19 +39,19 @@ class OpusOption { class OpusEvent { /// 事件类型 final String event; - + /// 数据类型 (file或stream) final String? type; - + /// 解码后的数据 (仅在流解码时有效) final Uint8List? data; - + /// 输出文件路径 (仅在文件解码完成时有效) final String? filePath; - + /// 错误码 (仅在发生错误时有效) final int? errorCode; - + /// 错误信息 (仅在发生错误时有效) final String? errorMessage; @@ -80,12 +80,16 @@ class OpusEvent { /// 杰理OPUS编解码插件 class JlOpus { - static const MethodChannel _methodChannel = MethodChannel('com.yunqiinnovation.jl_opus'); - static const EventChannel _eventChannel = EventChannel('com.yunqiinnovation.jl_opus/events'); - + static const MethodChannel _methodChannel = + MethodChannel('com.yunqiinnovation.jl_opus'); + static const EventChannel _eventChannel = + EventChannel('com.yunqiinnovation.jl_opus/events'); + static const EventChannel _eventChannel1 = + EventChannel('com.yunqiinnovation.jl_opus/events1'); + /// 事件流 Stream? _eventStream; - + /// 获取事件流 Stream get eventStream { _eventStream ??= _eventChannel.receiveBroadcastStream().map((event) { @@ -99,18 +103,19 @@ class JlOpus { }); return _eventStream!; } - + /// 解码后的数据流 - final StreamController _decodedDataController = StreamController.broadcast(); - + final StreamController _decodedDataController = + StreamController.broadcast(); + /// 获取解码数据流 Stream get decodedDataStream => _decodedDataController.stream; - + /// 构造函数,设置事件监听 JlOpus() { _setupEventListener(); } - + /// 设置事件监听 void _setupEventListener() { eventStream.listen((event) { @@ -119,7 +124,7 @@ class JlOpus { } }); } - + /// 初始化OPUS解码器 Future initOpusDecoder() async { try { @@ -129,13 +134,14 @@ class JlOpus { return false; } } - + /// 解码OPUS文件 - /// + /// /// [inPath] 输入文件路径 /// [outPath] 输出文件路径 /// [option] 解码参数 - Future decodeOpusFile(String inPath, String outPath, OpusOption option) async { + Future decodeOpusFile( + String inPath, String outPath, OpusOption option) async { try { final result = await _methodChannel.invokeMethod( 'decodeOpusFile', @@ -150,9 +156,9 @@ class JlOpus { rethrow; } } - + /// 开始解码OPUS数据流 - /// + /// /// [option] 解码参数 Future startDecodeStream(OpusOption option) async { try { @@ -165,19 +171,20 @@ class JlOpus { rethrow; } } - + /// 停止解码OPUS数据流 Future stopDecodeStream() async { try { - final result = await _methodChannel.invokeMethod('stopDecodeStream'); + final result = + await _methodChannel.invokeMethod('stopDecodeStream'); return result ?? false; } catch (e) { return false; } } - + /// 写入OPUS音频数据进行解码 - /// + /// /// [data] OPUS音频数据 Future writeAudioStream(Uint8List data) async { try { @@ -190,7 +197,7 @@ class JlOpus { rethrow; } } - + /// 判断是否正在解码 Future isDecoding() async { try { @@ -200,7 +207,7 @@ class JlOpus { return false; } } - + /// 释放资源 Future dispose() async { try {