Browse Source

Merge commit '3452269b5a906501e524a98f24f31ccf294cb673' into new_dev

newdev_shunjiawei
lxm 1 year ago
parent
commit
5845a6a308
  1. 75
      lib/core/utils/synchrodata.dart
  2. 7
      lib/data/services/asr_service.dart
  3. 2
      lib/data/services/speech_impl/azure_asr_service.dart
  4. 2
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  5. 2
      lib/data/services/speech_impl/volcano_asr_service.dart
  6. 2
      lib/data/services/speech_impl/xunfei_asr_service.dart
  7. 1
      lib/modules/agent/controllers/agent_controller.dart
  8. 7
      lib/modules/meeting/controllers/meeting_controller.dart
  9. 24
      lib/modules/meeting/controllers/meeting_details_controller.dart
  10. 35
      lib/modules/translation/controllers/translation_controller.dart
  11. 204
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  12. 10
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  13. 10
      local_plugins/agent_service/lib/agent_service.dart
  14. 123
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  15. 13
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  16. 8
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
  17. 3
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt
  18. 2
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt

75
lib/core/utils/synchrodata.dart

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import '../../data/services/db/database_helper.dart';
import '../../data/services/db/sqflite_api.dart';
import '../../data/services/network/api.dart';
@ -112,6 +113,80 @@ class Synchrodata {
// 同步数据
static Future _synchrodata() async {
final db = await DatabaseHelper().database;
var result = await Api.getOperationRecord();
List records = result['records'];
await db.transaction((txn) async {
for (var element in records) {
final List existList = await txn.query(
'meeting',
where: 'id = ?',
whereArgs: [element['number']],
);
Map<String, Object?> meetingData = {
'id': element['number'],
'title': element['title'] ?? '',
'type': element['rtype'] ?? '',
'seconds': element['seconds'] ?? 0,
'filepath': '',
'audiourl': element['audiourl'] ?? '',
'tasktype': element['tasktype'] ?? 0,
'creationtime': element['creationtime'] ?? 0,
};
Map<String, Object?> meetingDetailsData = {
'meetingid': element['number'],
'address': element['address'] ?? '',
'personnel': element['personnel'] ?? '',
'taskid': element['taskid'] ?? '',
'tasktype': element['tasktype'] ?? 0,
'overview': element['overview'] ?? '',
'summary': element['summary'] ?? '',
'mindmap': '',
};
if (existList.isNotEmpty) {
await txn.update(
'meeting',
meetingData,
where: 'id = ?',
whereArgs: [element['number']],
);
await txn.update(
'meetingdetails',
meetingDetailsData,
where: 'meetingid = ?',
whereArgs: [element['number']],
);
} else {
await txn.insert('meeting', meetingData);
await txn.insert('meetingdetails', meetingDetailsData);
}
if (element['original'].isNotEmpty) {
final List existSpeakerList = await txn.query(
'meetingspeaker',
where: 'meetingid = ?',
whereArgs: [element['number']],
);
List speakerList = jsonDecode(element['original']);
if (existSpeakerList.isNotEmpty) {
final batch = txn.batch();
for (var item in speakerList) {
batch.update(
'meetingspeaker',
item,
where: 'id = ?',
whereArgs: [item['id']],
);
}
await batch.commit();
} else {
final batch = txn.batch();
for (var item in speakerList) {
batch.insert('meetingspeaker', item);
}
await batch.commit();
}
}
}
});
}
}

7
lib/data/services/asr_service.dart

@ -26,11 +26,16 @@ abstract class AsrService {
/// 停止连续语音识别
Future<bool> stopContinuousRecognition();
/// 停止音频输入
Future<void> setPause();
Future<void> setcontinue();
/// 继续音频输入
Future<void> setContinue();
/// 恢复原始音频状态
Future<void> restoreOriginalAudioState();
/// 禁用蓝牙音频
Future<void> disableBluetoothAudio();
/// 检查连续识别是否活跃

2
lib/data/services/speech_impl/azure_asr_service.dart

@ -429,7 +429,7 @@ class AzureAsrService extends GetxService implements AsrService {
}
@override
Future<void> setcontinue() async {
Future<void> setContinue() async {
// TODO: implement setcontinue
try {
final bool result = await _channel.invokeMethod('setcontinue');

2
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -849,7 +849,7 @@ class VolcanoAsrApiService implements AsrService {
}
@override
Future<void> setcontinue() {
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}

2
lib/data/services/speech_impl/volcano_asr_service.dart

@ -384,7 +384,7 @@ class VolcanoAsrService extends GetxService implements AsrService {
}
@override
Future<void> setcontinue() {
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}

2
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -308,7 +308,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
@override
Future<void> setcontinue() {
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}

1
lib/modules/agent/controllers/agent_controller.dart

@ -176,6 +176,7 @@ class AgentController extends GetxController {
@override
void onClose() {
AgentService.stopAiSteam(); //停止AI流
AgentService.stopConversation(); // 停止当前会话
AgentService.stopTts(); // 停止当前语音
textController.dispose();

7
lib/modules/meeting/controllers/meeting_controller.dart

@ -59,7 +59,7 @@ class MeetingController extends GetxController {
'Authorization': 'Bearer; $_token',
'Content-Type': 'application/json',
};
_getDataList();
_initGetDataList();
addMeetingAudio();
final Map<String, dynamic> args = Get.arguments ?? {};
// 检查参数是否存在且包含有效路径
@ -96,6 +96,11 @@ class MeetingController extends GetxController {
}
}
void _initGetDataList() async {
await Synchrodata.uploadOperationRecords(isSynchro: true);
_getDataList();
}
// 请求本地数据
void _getDataList() async {
List list = await SqfliteApi.getMeetingList((page - 1) * 10 + addQuantity);

24
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -152,6 +152,21 @@ class MeetingDetailsController extends GetxController
dragOffset.value = 0;
isTop.value = true;
}
if (meetingData.value.filepath.isNotEmpty) {
await _loadPlayerController();
}
if (meetingDetails.value.tasktype >= 2) {
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList);
}
if (meetingDetails.value.tasktype == 1 ||
meetingDetails.value.tasktype == 2) {
summaryCreate();
}
isLoading.value = false;
}
Future<void> _loadPlayerController() async {
await playerController.preparePlayer(
path: meetingData.value.filepath,
shouldExtractWaveform: false,
@ -178,15 +193,6 @@ class MeetingDetailsController extends GetxController
lottieController.stop();
}
});
if (meetingDetails.value.tasktype >= 2) {
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList);
}
if (meetingDetails.value.tasktype == 1 ||
meetingDetails.value.tasktype == 2) {
summaryCreate();
}
isLoading.value = false;
}
void _loadJS() async {

35
lib/modules/translation/controllers/translation_controller.dart

@ -37,10 +37,13 @@ class TranslationController extends GetxController {
final bleManager = Get.find<BleManager>();
// 音频输入源
bool _audioSourceType = false;
// 倒计时器
bool istts = false;
//是否开启播放
bool isPlayback = false;
//录音计时器
final RxString recordDurationText = '00:00'.obs;
//记录计时器
Timer? _recordTimer;
//秒
int _seconds = 0;
// 存储相关
static const String _historyKey = 'translation_history';
@ -99,23 +102,24 @@ class TranslationController extends GetxController {
activeSpeaker.value = id;
// 监听识别结果
if (activeSpeaker.value != 0) {
istts = false;
_asrService.setcontinue();
isPlayback = false;
_asrService.setContinue();
lastActiveSpeaker.value = id;
} else {
_asrService.setPause();
if (fTFTranslationResult != '' && !istts) {
istts = true;
if (fTFTranslationResult != '' && !isPlayback) {
isPlayback = true;
await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
await Future.delayed(const Duration(milliseconds: 1300));
await playTranslatedText(fTFTranslationResult!);
fTFTranslationResult = '';
}
}
}
// 切换TTS功能
// 禁用蓝牙音频
void disableBluetoothAudio() => _asrService.disableBluetoothAudio();
// 切换TTS功能
//恢复原始音频状态
void restoreOriginalAudioState() => _asrService.restoreOriginalAudioState();
// 兼容旧版视图的计算属性
@ -161,7 +165,7 @@ class TranslationController extends GetxController {
}
// 加载历史记录
loadTranslationHistory();
//loadTranslationHistory();
// 初始化服务
_initServices();
@ -379,7 +383,8 @@ class TranslationController extends GetxController {
if (currentMode.value == 'faceToFace') {
_asrService.setPause();
} else {
_asrService.setcontinue();
await _ttsService.setAudioOutputDevice(0);
_asrService.setContinue();
}
isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) {
@ -469,6 +474,7 @@ class TranslationController extends GetxController {
isIntermediate: true,
);
// 添加新项后滚动到底部
translationHistory.add(newItem);
_scrollToBottom();
} else {
// 更新当前项
@ -576,19 +582,20 @@ class TranslationController extends GetxController {
fTFTranslationResult =
(fTFTranslationResult ?? '') + translationResult;
if (activeSpeaker.value == 0 && !istts) {
istts = true;
if (activeSpeaker.value == 0 &&
!isPlayback &&
currentMode.value == 'faceToFace') {
isPlayback = true;
await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
await Future.delayed(const Duration(milliseconds: 1300));
await playTranslatedText(fTFTranslationResult!);
fTFTranslationResult = '';
;
}
}
// 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value &&
isFinal &&
currentMode.value != 'faceToFace') {
_ttsService.setAudioOutputDevice(activeSpeaker.value);
await playTranslatedText(translationResult);
}
} else {

204
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -26,6 +26,7 @@ import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.bytedance_speech.BytedanceTTS
import java.util.UUID
import com.yunqiinnovation.agent_service.Utils
/**
* 代理服务事件监听接口
@ -571,7 +572,7 @@ object AgentService : CoroutineScope {
/**
* 停止AI流输出
*/
private fun stopAiStream() {
fun stopAiStream() {
if (isAiStreaming) {
try {
// 先更新状态,避免回调时的状态不一致
@ -580,7 +581,8 @@ object AgentService : CoroutineScope {
// 取消当前AI生成任务
currentAiJob?.cancel()
currentAiJob = null
currsessionId = "" //取消会话
// 通知ChatAPI服务终止当前流式请求
chatApiService.cancelCurrentStream()
@ -756,9 +758,6 @@ object AgentService : CoroutineScope {
responseBuilder.append(token)
if (speakResponse && broadcast) {
ttsService?.speakStream(token)
if (token.length > 0) {
audioPlayer?.stopAudio()
}
}
if (broadcast) {
// 发送流式回复token
@ -774,6 +773,7 @@ object AgentService : CoroutineScope {
// 视情况决定是否朗读回复
if (speakResponse && broadcast && sessionid == currsessionId){
ttsService?.flushStream()
audioPlayer?.stopAudio()
}
val response = responseBuilder.toString()
// 发送完整回复,包含是否有图片的标记
@ -1164,6 +1164,22 @@ object AgentService : CoroutineScope {
class AudioPlayer(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null
private var isInitialized = false
private val playbackLock = Object() // 添加同步锁
/**
* 安全检查 MediaPlayer 是否正在播放
*/
private fun isMediaPlayerPlaying(): Boolean {
return try {
mediaPlayer?.isPlaying == true
} catch (e: IllegalStateException) {
Log.w(TAG, "MediaPlayer 状态异常,假定未播放: ${e.message}")
false
} catch (e: Exception) {
Log.w(TAG, "检查播放状态时发生异常: ${e.message}")
false
}
}
/**
* 播放音频资源
@ -1172,75 +1188,167 @@ object AgentService : CoroutineScope {
* @param volume 音量大小,范围0.0-1.0,默认1.0
*/
fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) {
try {
// 释放之前的资源
release()
synchronized(playbackLock) {
try {
// 强制停止并释放之前的资源
forceStop()
// 创建播放器并设置资源
mediaPlayer = MediaPlayer().apply {
// 设置资源
context.resources.openRawResourceFd(resId)?.use { fd ->
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
}
// 创建播放器并设置资源
mediaPlayer = MediaPlayer().apply {
// 设置资源
context.resources.openRawResourceFd(resId)?.use { fd ->
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
}
this.isLooping = isLooping // 设置循环属性
setVolume(volume, volume) // 设置音量(左声道,右声道)
this.isLooping = isLooping // 设置循环属性
setVolume(volume, volume) // 设置音量(左声道,右声道)
setOnCompletionListener {
if (!isLooping) {
release()
setOnCompletionListener {
if (!isLooping) {
release()
}
}
setOnErrorListener { _, what, extra ->
Log.e(TAG, "MediaPlayer 错误: what=$what, extra=$extra")
forceRelease()
true // 返回true表示错误已处理
}
}
prepare()
start()
prepare()
start()
Log.d(TAG, "开始播放音频资源: $resId, 循环: $isLooping")
}
isInitialized = true
} catch (e: Exception) {
Log.e(TAG, "播放音频资源异常: ${e.message}", e)
forceRelease()
}
} catch (e: Exception) {
Log.e(TAG, "播放音频资源异常: ${e.message}", e)
release()
}
}
/**
* 停止音频播放
*/
fun stopAudio() {
try {
mediaPlayer?.apply {
when {
isPlaying -> {
synchronized(playbackLock) {
try {
mediaPlayer?.apply {
if (isMediaPlayerPlaying()) {
stop()
Log.d(TAG, "音频已停止")
}
else -> {
} else {
Log.d(TAG, "音频未在播放状态,无需停止")
}
}
} catch (e: IllegalStateException) {
Log.e(TAG, "MediaPlayer 状态异常,无法停止: ${e.message}", e)
forceRelease()
} catch (e: Exception) {
Log.e(TAG, "停止音频播放异常: ${e.message}", e)
forceRelease()
}
} catch (e: IllegalStateException) {
Log.e(TAG, "MediaPlayer 状态异常,无法停止: ${e.message}", e)
// 重置 MediaPlayer
release()
} catch (e: Exception) {
Log.e(TAG, "停止音频播放异常: ${e.message}", e)
}
}
/**
* 释放资源
* 强制停止播放(不释放资源)
*/
fun release() {
private fun forceStop() {
try {
mediaPlayer?.apply {
if (isPlaying) {
stop()
if (isMediaPlayerPlaying()) {
try {
stop()
Log.d(TAG, "强制停止音频播放")
} catch (e: Exception) {
Log.w(TAG, "强制停止时异常: ${e.message}")
}
}
try {
reset()
} catch (e: Exception) {
Log.w(TAG, "重置时异常: ${e.message}")
}
reset()
release()
}
mediaPlayer = null
isInitialized = false
} catch (e: Exception) {
Log.e(TAG, "释放音频资源异常: ${e.message}", e)
mediaPlayer = null
isInitialized = false
Log.w(TAG, "强制停止过程中异常: ${e.message}")
}
}
/**
* 释放资源
*/
fun release() {
synchronized(playbackLock) {
try {
mediaPlayer?.apply {
// 安全停止播放
if (isMediaPlayerPlaying()) {
try {
stop()
} catch (e: IllegalStateException) {
Log.w(TAG, "停止播放时状态异常: ${e.message}")
}
}
// 安全重置
try {
reset()
} catch (e: IllegalStateException) {
Log.w(TAG, "重置 MediaPlayer 时状态异常: ${e.message}")
}
// 安全释放 - 修复递归调用问题
try {
release() // 这里调用的是 MediaPlayer.release(),不是递归
} catch (e: Exception) {
Log.w(TAG, "释放 MediaPlayer 时异常: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "释放音频资源异常: ${e.message}", e)
} finally {
// 确保变量被清理
mediaPlayer = null
isInitialized = false
}
}
}
/**
* 强制释放资源,不进行状态检查
*/
private fun forceRelease() {
synchronized(playbackLock) {
try {
mediaPlayer?.apply {
try {
reset()
} catch (e: Exception) {
Log.w(TAG, "重置 MediaPlayer 失败: ${e.message}")
}
try {
release()
} catch (e: Exception) {
Log.w(TAG, "释放 MediaPlayer 失败: ${e.message}")
}
}
} catch (e: Exception) {
Log.w(TAG, "强制释放资源时发生异常: ${e.message}")
} finally {
mediaPlayer = null
isInitialized = false
}
}
}
/**
* 检查是否正在播放
*/
fun isPlaying(): Boolean {
synchronized(playbackLock) {
return isMediaPlayerPlaying()
}
}
}

10
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -177,6 +177,16 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("STOP_TTS_ERROR", "停止语音合成失败: ${e.message}", null)
}
}
"stopAiSteam" -> {
try {
AgentService.stopAiStream()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "停止语音合成失败", e)
result.error("STOP_TTS_ERROR", "停止语音合成失败: ${e.message}", null)
}
}
"clearChatHistory" -> {
try {
AgentService.clearChatHistory { success ->

10
local_plugins/agent_service/lib/agent_service.dart

@ -293,6 +293,16 @@ class AgentService {
}
}
/// 停止语音合成
static Future<bool> stopAiSteam() async {
try {
final bool result = await _channel.invokeMethod('stopAiSteam');
return result;
} on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止语音合成失败', e.details);
}
}
/// 停止语音合成
static Future<bool> stopTts() async {
try {

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

@ -81,6 +81,30 @@ class AzureAsrHelper(private val context: Context) {
EXTERNAL
}
/**
* 获取设备支持的最佳音频格式
* 优先选择16000Hz,若不支持则降级到8000Hz
*/
private fun getOptimalAudioFormat(): AudioStreamFormat {
// 支持的采样率列表(按优先级排序)
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
val bufferSize = AudioRecord.getMinBufferSize(
rate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
Log.i(tag, "使用采样率: ${sampleRate}Hz")
// 创建对应的音频格式
return AudioStreamFormat.getWaveFormatPCM(sampleRate.toLong(), 16, 1)
}
/**
* 初始化Azure语音服务
*
@ -106,7 +130,7 @@ class AzureAsrHelper(private val context: Context) {
// 释放之前的资源
dispose()
// 1. 创建可控制的音频流
val format = AudioStreamFormat.getWaveFormatPCM(16000, 16, 1)
val format = getOptimalAudioFormat()
audioStream = AudioInputStream.createPushStream(format)
// 初始化音频管理器
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
@ -157,7 +181,7 @@ class AzureAsrHelper(private val context: Context) {
// 录音文件类
recordfile = RecordFile;
//进入界面手机麦克风就被占用,导致其他app无法使用麦克风,在开始录音再去申请
// 创建识别器
// // 创建识别器
// val setupSuccess = setupRecognizer()
// if (setupSuccess) {
// // 优化:初始化完成后进行预热
@ -370,6 +394,7 @@ class AzureAsrHelper(private val context: Context) {
startCaptureThread() // 再启动数据读取线程
isContinuousRecognitionActive = true
return true
} catch (e: Exception) {
stopAudioProcessing()
@ -483,7 +508,8 @@ class AzureAsrHelper(private val context: Context) {
// 停止音频处理
stopAudioProcessing()
// 停止录音
stopMicrophoneCapture()
// 尝试强制关闭识别器
try {
recognizer?.close()
@ -705,8 +731,19 @@ class AzureAsrHelper(private val context: Context) {
* 开始麦克风捕获
*/
private fun initAudioRecord() {
// 设置音频参数
val sampleRate = 16000 // 16kHz采样率(语音识别常用)
// 获取已确定的采样率
val format = getOptimalAudioFormat()
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
val bufferSize = AudioRecord.getMinBufferSize(
rate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
val channelConfig = AudioFormat.CHANNEL_IN_MONO // 单声道输入
val audioFormat = AudioFormat.ENCODING_PCM_16BIT // 16位PCM格式
@ -716,49 +753,73 @@ class AzureAsrHelper(private val context: Context) {
channelConfig,
audioFormat
)
// 创建录音对象
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Log.d("AzureASR", "android.os.Build.VERSION.SDK_INT")
val format = AudioFormat.Builder()
.setSampleRate(sampleRate)
.setEncoding(audioFormat)
.setChannelMask(channelConfig)
.build()
audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setAudioFormat(format)
.setBufferSizeInBytes(minBufferSize * 2)
.build()
} else {
// 创建 AudioRecord 对象
audioRecord = AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION, // 语音识别专用音源(自带降噪/回声消除)
sampleRate,
channelConfig,
audioFormat,
minBufferSize * 2 // 使用两倍缓冲区防止溢出
)
// 创建 AudioRecord 对象
audioRecord = AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION, // 语音识别专用音源(自带降噪/回声消除)
sampleRate,
channelConfig,
audioFormat,
minBufferSize * 2 // 使用两倍缓冲区防止溢出
)
}
// 检查初始化状态
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
throw IllegalStateException("AudioRecord初始化失败")
}
// 应用音频效果
applyAudioEffects()
}
// 3. 单独封装线程启动逻辑
private fun startCaptureThread() {
captureThread = Thread {
// 循环条件确保录音已开始
// 根据实际采样率计算缓冲区大小
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
var bufferSize = 0
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
bufferSize = AudioRecord.getMinBufferSize(
rate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
val buffer = ByteArray(bufferSize)
val buffer = ByteArray(1024) // 4KB数据缓冲区
while (!Thread.interrupted() && audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
// 从麦克风读取数据
// 读取音频
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0
// 仅在未暂停时处理数据
if (!isPaused && bytesRead > 0) {
// 写入音频流(可能是网络传输或本地处理)
// 写入Azure流
audioStream?.write(buffer)
// 保存到WAV文件(如果启用了录制功能)
recordfile?.saveAudioDataToWav(buffer)
}
// 暂停时短暂休眠以减少CPU占用
if (isPaused) {
try {
Thread.sleep(50)
} catch (e: InterruptedException) {
break // 线程被中断时退出循环
}
// 保存录音
recordfile?.saveAudioDataToWav(buffer)
}
}
}.apply { start() }
}

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

@ -257,20 +257,24 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"setPause" -> {
azureAsrHelper.stopAudioPause();
return
}
"setcontinue" -> {
// azureAsrHelper.stopAudioPause();
azureAsrHelper.setAudiocontinue();
return
}
"disableBluetoothAudio" -> {
azureAsrHelper.disableBluetoothAudio();
return
}
"restoreOriginalAudioState" -> {
// azureAsrHelper.stopAudioPause();
azureAsrHelper.restoreOriginalAudioState();
return
}
"startContinuousRecognition" -> {
@ -523,19 +527,20 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"setAudioOutputDevice" -> {
val type = call.argument<Int>("type")
var success =false
if (type == 0) {
// 默认(如果有耳机选耳机,否则使用系统扬声器)
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT)
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT)
} else if (type == 1) {
// 强制使用声器
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
} else if (type == 2) {
// 强制使用耳机
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES)
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES)
}
result.success(true)
result.success(success)
}
else -> {

8
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -210,9 +210,10 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
*
* @param device 音频输出设备类型
*/
override fun setAudioOutputDevice(device: AudioOutputDevice) {
override fun setAudioOutputDevice(device: AudioOutputDevice): Boolean{
// Azure TTS使用系统默认的音频路由
// 音频输出设备的控制需要通过Android的AudioManager实现
try {
FileLogger.e(TAG, "音频输出设备类型device=: ${device}")
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
audioManager?.let { manager ->
@ -244,6 +245,11 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
}
}
}
return true
} catch (e: Exception) {
FileLogger.e(TAG, "设置音频输出设备失败: ${e.message}")
return false
}
}
/**

3
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt

@ -425,10 +425,11 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
/**
* 设置音频输出设备
*/
override fun setAudioOutputDevice(device: AudioOutputDevice) {
override fun setAudioOutputDevice(device: AudioOutputDevice) : Boolean{
if (useInternalPlayer) {
audioPlayer.setAudioOutputDevice(device)
}
return true;
}
/**

2
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt

@ -103,5 +103,5 @@ interface ITtsService {
*
* @param device 音频输出设备类型
*/
fun setAudioOutputDevice(device: AudioOutputDevice)
fun setAudioOutputDevice(device: AudioOutputDevice): Boolean
}
Loading…
Cancel
Save