wolfplus 1 year ago
parent
commit
18429810a4
  1. 2
      .bundle/config
  2. 1
      .cursorrules
  3. 3
      Gemfile
  4. 106
      Gemfile.lock
  5. 3
      lib/core/bindings/initial_binding.dart
  6. 1
      lib/data/services/ble_manager.dart
  7. 56
      lib/modules/agent/controllers/agent_controller.dart
  8. 9
      lib/modules/opus_test/bindings/opus_test_binding.dart
  9. 404
      lib/modules/opus_test/controllers/opus_test_controller.dart
  10. 445
      lib/modules/opus_test/views/opus_test_view.dart
  11. 114
      lib/modules/pairing/controllers/pairing_controller.dart
  12. 24
      lib/modules/settings/views/settings_view.dart
  13. 10
      lib/routes/app_pages.dart
  14. 864
      local_plugins/agent_service/ios/Classes/AgentServiceImpl.swift
  15. 2
      local_plugins/agent_service/ios/Classes/AgentServicePlugin.h
  16. 303
      local_plugins/agent_service/ios/Classes/AgentServicePlugin.swift
  17. 240
      local_plugins/agent_service/ios/Classes/AudioSessionHub.swift
  18. BIN
      local_plugins/agent_service/ios/Resources/calling.mp3
  19. BIN
      local_plugins/agent_service/ios/Resources/start.mp3
  20. BIN
      local_plugins/agent_service/ios/Resources/stop.mp3
  21. 15
      local_plugins/agent_service/ios/agent_service.podspec
  22. 1
      local_plugins/agent_service/lib/agent_service.dart
  23. 13
      local_plugins/agent_service/pubspec.yaml
  24. 2
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
  25. 673
      local_plugins/azure_speech/ios/Classes/AzureAsrHelper.swift
  26. 166
      local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.swift
  27. 257
      local_plugins/azure_speech/ios/Classes/AzureTtsHelper.swift
  28. 2
      local_plugins/azure_speech/ios/azure_speech.podspec
  29. 406
      local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift
  30. 464
      local_plugins/ble_service/ios/Classes/BleService.swift
  31. 128
      local_plugins/ble_service/ios/Classes/OpusAudioProcessor.swift
  32. 184
      local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift
  33. 151
      local_plugins/ble_service/ios/Classes/SwiftOpusAudioProcessor.swift
  34. 92
      local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Headers/OpusUnit.h
  35. 20
      local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Headers/SpeexKit.h
  36. 40
      local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Headers/SpeexUnit.h
  37. BIN
      local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Info.plist
  38. 6
      local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Modules/module.modulemap
  39. BIN
      local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/SpeexKit
  40. 8
      local_plugins/ble_service/ios/ble_service.podspec
  41. 126
      local_plugins/jl_opus/README.md
  42. 62
      local_plugins/jl_opus/android/build.gradle.kts
  43. 1
      local_plugins/jl_opus/android/settings.gradle.kts
  44. 9
      local_plugins/jl_opus/android/src/main/AndroidManifest.xml
  45. 426
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt
  46. BIN
      local_plugins/jl_opus/android/src/main/libs/jl_audio_decode_V2.0.0_20005.aar
  47. 221
      local_plugins/jl_opus/lib/jl_opus.dart
  48. 25
      local_plugins/jl_opus/pubspec.yaml
  49. 253
      local_plugins/open_ai_service/README.md
  50. 83
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt
  51. 477
      local_plugins/open_ai_service/ios/Classes/MCPClient.swift
  52. 1315
      local_plugins/open_ai_service/ios/Classes/OpenAIService.swift
  53. 594
      local_plugins/open_ai_service/ios/Classes/OpenAIServicePlugin.swift
  54. 760
      local_plugins/open_ai_service/ios/Classes/SystemFunctionHandler.swift
  55. 22
      local_plugins/open_ai_service/ios/open_ai_service.podspec
  56. 2
      local_plugins/open_ai_service/lib/open_ai_service.dart
  57. BIN
      local_plugins/opus/.gradle/8.10/checksums/checksums.lock
  58. BIN
      local_plugins/opus/.gradle/8.10/fileChanges/last-build.bin
  59. BIN
      local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.bin
  60. BIN
      local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock
  61. BIN
      local_plugins/opus/.gradle/8.9/checksums/checksums.lock
  62. BIN
      local_plugins/opus/.gradle/8.9/fileChanges/last-build.bin
  63. BIN
      local_plugins/opus/.gradle/8.9/fileHashes/fileHashes.lock
  64. BIN
      local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock
  65. 2
      local_plugins/opus/.gradle/buildOutputCleanup/cache.properties
  66. 1
      local_plugins/opus/.gradle/nb-cache/trust/404236D79074CDA6A1B2372C665DCA9E587766B94C3493E9904CD4249D51B6BF
  67. 0
      local_plugins/opus/.gradle/vcs-1/gc.properties
  68. 92
      local_plugins/opus/README.md
  69. 21
      local_plugins/opus/android/CMakeLists.txt
  70. 67
      local_plugins/opus/android/build.gradle.kts
  71. 6
      local_plugins/opus/android/libs/opus/AUTHORS
  72. 744
      local_plugins/opus/android/libs/opus/CMakeLists.txt
  73. 44
      local_plugins/opus/android/libs/opus/COPYING
  74. 0
      local_plugins/opus/android/libs/opus/ChangeLog
  75. 368
      local_plugins/opus/android/libs/opus/INSTALL
  76. 489
      local_plugins/opus/android/libs/opus/Makefile.am
  77. 4853
      local_plugins/opus/android/libs/opus/Makefile.in
  78. 169
      local_plugins/opus/android/libs/opus/Makefile.mips
  79. 167
      local_plugins/opus/android/libs/opus/Makefile.unix
  80. 0
      local_plugins/opus/android/libs/opus/NEWS
  81. 189
      local_plugins/opus/android/libs/opus/README
  82. 1214
      local_plugins/opus/android/libs/opus/aclocal.m4
  83. 182
      local_plugins/opus/android/libs/opus/celt/_kiss_fft_guts.h
  84. 291
      local_plugins/opus/android/libs/opus/celt/arch.h
  85. 353
      local_plugins/opus/android/libs/opus/celt/arm/arm2gnu.pl
  86. 171
      local_plugins/opus/android/libs/opus/celt/arm/arm_celt_map.c
  87. 291
      local_plugins/opus/android/libs/opus/celt/arm/armcpu.c
  88. 90
      local_plugins/opus/android/libs/opus/celt/arm/armcpu.h
  89. 37
      local_plugins/opus/android/libs/opus/celt/arm/armopts.s.in
  90. 173
      local_plugins/opus/android/libs/opus/celt/arm/celt_fft_ne10.c
  91. 258
      local_plugins/opus/android/libs/opus/celt/arm/celt_mdct_ne10.c
  92. 272
      local_plugins/opus/android/libs/opus/celt/arm/celt_neon_intr.c
  93. 555
      local_plugins/opus/android/libs/opus/celt/arm/celt_pitch_xcorr_arm-gnu.S
  94. 551
      local_plugins/opus/android/libs/opus/celt/arm/celt_pitch_xcorr_arm.s
  95. 71
      local_plugins/opus/android/libs/opus/celt/arm/fft_arm.h
  96. 35
      local_plugins/opus/android/libs/opus/celt/arm/fixed_arm64.h
  97. 80
      local_plugins/opus/android/libs/opus/celt/arm/fixed_armv4.h
  98. 151
      local_plugins/opus/android/libs/opus/celt/arm/fixed_armv5e.h
  99. 121
      local_plugins/opus/android/libs/opus/celt/arm/kiss_fft_armv4.h
  100. 118
      local_plugins/opus/android/libs/opus/celt/arm/kiss_fft_armv5e.h

2
.bundle/config

@ -0,0 +1,2 @@
---
BUNDLE_PATH: "vendor/bundle"

1
.cursorrules

@ -26,6 +26,7 @@
• 并发场景首选 Kotlin 协程;仅在 Flutter 端按需使用 Dart Future / Stream。 • 并发场景首选 Kotlin 协程;仅在 Flutter 端按需使用 Dart Future / Stream。
3. 语言 3. 语言
• 与用户交流、注释及文档一律使用中文。 • 与用户交流、注释及文档一律使用中文。
• 不要自己创建总结文档。
宗旨:保持极简,先跑通业务,再考虑优化。 宗旨:保持极简,先跑通业务,再考虑优化。

3
Gemfile

@ -0,0 +1,3 @@
source "https://rubygems.org"
gem 'cocoapods', '~> 1.16'
gem 'cocoapods-spm', '~> 0.1.15'

106
Gemfile.lock

@ -0,0 +1,106 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
activesupport (6.1.7.10)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
base64 (0.2.0)
claide (1.1.0)
cocoapods (1.16.2)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.16.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.27.0, < 2.0)
cocoapods-core (1.16.2)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (2.1)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-spm (0.1.15)
xcodeproj (>= 1.23.0)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.3.5)
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
ffi (1.17.2)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.9.0)
mutex_m
i18n (1.14.7)
concurrent-ruby (~> 1.0)
json (2.7.6)
minitest (5.25.4)
molinillo (0.8.0)
mutex_m (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
netrc (0.11.0)
nkf (0.2.0)
public_suffix (4.0.7)
rexml (3.4.1)
ruby-macho (2.5.1)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
zeitwerk (2.6.18)
PLATFORMS
ruby
DEPENDENCIES
cocoapods (~> 1.16)
cocoapods-spm (~> 0.1.15)
BUNDLED WITH
1.17.2

3
lib/core/bindings/initial_binding.dart

@ -7,6 +7,7 @@ import '../../data/services/language_manager.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../data/services/bluetooth_manager.dart'; import '../../data/services/bluetooth_manager.dart';
import '../../data/services/ble_manager.dart'; import '../../data/services/ble_manager.dart';
import 'package:agent_service/agent_service.dart';
/// 初始绑定,用于管理全局依赖 /// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings { class InitialBinding extends Bindings {
@ -35,6 +36,8 @@ class InitialBinding extends Bindings {
Get.lazyPut<BleManager>(() => BleManager(), fenix: true); Get.lazyPut<BleManager>(() => BleManager(), fenix: true);
// MusicManager // MusicManager
Get.lazyPut<MusicManager>(() => MusicManager(), fenix: true); Get.lazyPut<MusicManager>(() => MusicManager(), fenix: true);
// 火山AI服务 // 火山AI服务
// Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true); // Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);

1
lib/data/services/ble_manager.dart

@ -151,7 +151,6 @@ class BleManager extends GetxService {
// 监听BLE状态更新 // 监听BLE状态更新
_statusSubscription = _bleService.onStatusUpdated.listen( _statusSubscription = _bleService.onStatusUpdated.listen(
(event) { (event) {
Logger.info('接收到BLE状态更新: $event');
if (event is Map) { if (event is Map) {
// 处理连接状态更新 // 处理连接状态更新

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

@ -197,19 +197,18 @@ class AgentController extends GetxController {
case AgentServiceEventType.recognitionStarted: case AgentServiceEventType.recognitionStarted:
isListening.value = true; isListening.value = true;
isTextInputMode.value = false; isTextInputMode.value = false;
isTextInputMode.refresh(); // isTextInputMode.refresh();
// 只标记识别开始,不添加消息 // 只标记识别开始,不添加消息
break; break;
case AgentServiceEventType.recognitionStopped: case AgentServiceEventType.recognitionStopped:
isListening.value = false; isListening.value = false;
isTextInputMode.value = true; isTextInputMode.value = true;
isTextInputMode.refresh(); // isTextInputMode.refresh();
break; break;
case AgentServiceEventType.recognizing: case AgentServiceEventType.recognizing:
final text = event.data['text'] ?? ''; final text = event.data['text'] ?? '';
currentText.value = text; // 保留当前文本,以便其他地方使用 currentText.value = text; // 保留当前文本,以便其他地方使用
if (text.isNotEmpty) { if (text.isNotEmpty) {
// 查找是否有正在识别中的消息 // 查找是否有正在识别中的消息
final index = messages final index = messages
@ -231,14 +230,13 @@ class AgentController extends GetxController {
isRecognizing: true, isRecognizing: true,
)); ));
} }
messages.refresh(); // messages.refresh();
// 使用reverse:true时通常不需要手动滚动
} }
break; break;
case AgentServiceEventType.recognitionResult: case AgentServiceEventType.recognitionResult:
final text = event.data['text'] ?? ''; final text = event.data['text'] ?? '';
Logger.i(TAG, '识别结果: $text');
// 查找是否有正在识别中的消息 // 查找是否有正在识别中的消息
final index = final index =
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser); messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
@ -266,7 +264,7 @@ class AgentController extends GetxController {
} else if (index >= 0) { } else if (index >= 0) {
// 如果识别结果为空,移除临时的识别消息 // 如果识别结果为空,移除临时的识别消息
messages.removeAt(index); messages.removeAt(index);
messages.refresh(); // messages.refresh();
} }
break; break;
@ -279,6 +277,7 @@ class AgentController extends GetxController {
case AgentServiceEventType.ttsStopped: case AgentServiceEventType.ttsStopped:
case AgentServiceEventType.ttsCanceled: case AgentServiceEventType.ttsCanceled:
isSpeaking.value = false; isSpeaking.value = false;
Logger.i(TAG, 'TTS停止播放'); Logger.i(TAG, 'TTS停止播放');
break; break;
@ -338,7 +337,7 @@ class AgentController extends GetxController {
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser); messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);
if (index >= 0) { if (index >= 0) {
messages.removeAt(index); messages.removeAt(index);
messages.refresh(); // messages.refresh();
} }
Logger.e(TAG, '代理服务错误: ${event.data['message']}'); Logger.e(TAG, '代理服务错误: ${event.data['message']}');
@ -362,7 +361,7 @@ class AgentController extends GetxController {
text: messages[index].text, text: messages[index].text,
timestamp: messages[index].timestamp, timestamp: messages[index].timestamp,
); );
messages.refresh(); // messages.refresh();
// 发送此消息到AI处理 // 发送此消息到AI处理
isProcessing.value = true; isProcessing.value = true;
@ -380,11 +379,26 @@ class AgentController extends GetxController {
case AgentServiceEventType.functionCallResult: case AgentServiceEventType.functionCallResult:
final responseId = event.data['responseId'] ?? ''; final responseId = event.data['responseId'] ?? '';
final result = event.data['result'] ?? ''; final result = event.data['result'];
final functionCall = event.data['function_call'] ?? ''; final functionCall = event.data['function_call'] ?? '';
Logger.i(TAG, 'Flutter 结束调用 mcp: $result, $functionCall'); Logger.i(TAG, 'Flutter 结束调用 mcp: $result, $functionCall');
if (result.isNotEmpty) { if (result != null) {
final parsedResult = jsonDecode(result); // result 可能已经是 Map 类型,也可能是 String 类型
Map<String, dynamic> parsedResult;
if (result is String && result.isNotEmpty) {
try {
parsedResult = jsonDecode(result);
} catch (e) {
Logger.e(TAG, '解析result数据失败: $e');
break;
}
} else if (result is Map<String, dynamic>) {
parsedResult = result;
} else {
Logger.e(TAG, 'result数据类型不支持: ${result.runtimeType}');
break;
}
final meta = parsedResult["meta"]; final meta = parsedResult["meta"];
Map<String, dynamic> metaResult = {}; Map<String, dynamic> metaResult = {};
@ -394,6 +408,8 @@ class AgentController extends GetxController {
} catch (e) { } catch (e) {
Logger.e(TAG, '解析meta数据失败: $e'); Logger.e(TAG, '解析meta数据失败: $e');
} }
} else if (meta != null && meta is Map<String, dynamic>) {
metaResult = meta;
} }
if(metaResult.isNotEmpty){ if(metaResult.isNotEmpty){
@ -533,18 +549,16 @@ class AgentController extends GetxController {
// 检查麦克风权限 // 检查麦克风权限
Future<bool> _checkMicrophonePermission() async { Future<bool> _checkMicrophonePermission() async {
try { try {
// iOS平台直接返回true,不处理权限问题
if (Platform.isIOS) {
return true;
}
// 创建待请求权限列表 // 创建待请求权限列表
List<Permission> permissionsToRequest = []; List<Permission> permissionsToRequest = [];
if (Platform.isAndroid) { // Android 需要考虑SDK版本不同的权限要求
// Android 需要考虑SDK版本不同的权限要求 permissionsToRequest.add(Permission.microphone);
permissionsToRequest.add(Permission.microphone);
} else if (Platform.isIOS) {
// iOS 只需要麦克风权限
permissionsToRequest.add(Permission.microphone);
}
// 批量请求所有需要的权限 // 批量请求所有需要的权限
final statuses = await permissionsToRequest.request(); final statuses = await permissionsToRequest.request();

9
lib/modules/opus_test/bindings/opus_test_binding.dart

@ -1,9 +0,0 @@
import 'package:get/get.dart';
import '../controllers/opus_test_controller.dart';
class OpusTestBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<OpusTestController>(() => OpusTestController());
}
}

404
lib/modules/opus_test/controllers/opus_test_controller.dart

@ -1,404 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:just_audio/just_audio.dart';
import 'package:jl_opus/jl_opus.dart';
import 'package:permission_handler/permission_handler.dart';
class OpusTestController extends GetxController {
// 选中的文件
final selectedFiles = <PlatformFile>[].obs;
// 是否携带协议头
final hasHeader = false.obs;
// 数据包长度
final packetLength = '40'.obs;
// 采样率选项
final sampleRateOptions = [8000, 16000, 24000, 32000, 44100, 48000];
final selectedSampleRate = 16000.obs;
// 通道数量
final isMono = true.obs;
// 播放状态
final isPlaying = false.obs;
final isDecoding = false.obs;
final statusMessage = ''.obs;
// 播放器
final player = AudioPlayer();
// 临时文件路径
String? tempPcmPath;
// 杰理OPUS解码器
late JlOpus jlOpus;
StreamSubscription? _eventSubscription;
@override
void onInit() {
super.onInit();
requestPermissions();
_initOpusDecoder();
}
@override
void onClose() {
player.dispose();
_eventSubscription?.cancel();
jlOpus.dispose();
super.onClose();
}
// 初始化OPUS解码器
Future<void> _initOpusDecoder() async {
jlOpus = JlOpus();
// 监听解码器事件
_eventSubscription = jlOpus.eventStream.listen((event) {
switch (event.event) {
case 'onStart':
statusMessage.value = '开始${event.type == "file" ? "文件" : "流"}解码...';
break;
case 'onComplete':
if (event.type == 'file') {
statusMessage.value = '解码完成: ${event.filePath}';
} else {
statusMessage.value = '流解码完成';
}
break;
case 'onError':
statusMessage.value = '解码错误: [${event.errorCode}] ${event.errorMessage}';
isDecoding.value = false;
break;
}
});
// 初始化OPUS解码器
final initialized = await jlOpus.initOpusDecoder();
if (!initialized) {
statusMessage.value = 'OPUS解码器初始化失败';
}
}
// 请求必要权限
Future<void> requestPermissions() async {
if (Platform.isAndroid) {
await Permission.storage.request();
}
}
// 选择文件
Future<void> pickFiles() async {
try {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['opus'],
allowMultiple: false,
);
if (result != null && result.files.isNotEmpty) {
selectedFiles.clear();
selectedFiles.add(result.files.first);
statusMessage.value = '已选择文件:${result.files.first.name}';
}
} catch (e) {
statusMessage.value = '选择文件失败: $e';
}
}
// 切换通道模式
void toggleChannel(bool isSingleChannel) {
isMono.value = isSingleChannel;
}
// 设置采样率
void setSampleRate(int rate) {
selectedSampleRate.value = rate;
}
// 设置数据包长度
void setPacketLength(String length) {
packetLength.value = length;
}
// 切换是否携带协议头
void toggleHeader(bool value) {
hasHeader.value = value;
}
// 播放音频
Future<void> playAudio() async {
if (isPlaying.value) {
await player.stop();
isPlaying.value = false;
statusMessage.value = '已停止播放';
return;
}
if (selectedFiles.isEmpty) {
statusMessage.value = '请先选择Opus文件';
return;
}
try {
isDecoding.value = true;
statusMessage.value = '正在解码...';
// 使用杰理OPUS库进行解码
final inPath = selectedFiles.first.path!;
// 创建WAV输出文件路径
final tempDir = await getTemporaryDirectory();
final wavFileName = 'decoded_opus_${DateTime.now().millisecondsSinceEpoch}.wav';
final outPath = '${tempDir.path}/$wavFileName';
// 创建解码参数
final option = OpusOption(
hasHeader: hasHeader.value,
channel: isMono.value ? 1 : 2,
sampleRate: selectedSampleRate.value,
packetSize: int.tryParse(packetLength.value) ?? 40,
);
// 解码文件
final pcmPath = await jlOpus.decodeOpusFile(inPath, outPath, option);
if (pcmPath == null) {
statusMessage.value = '解码失败';
isDecoding.value = false;
return;
}
tempPcmPath = pcmPath;
// 将PCM文件转换为WAV文件
final wavFile = await _convertPcmToWav(
pcmPath,
'${tempDir.path}/$wavFileName',
selectedSampleRate.value,
isMono.value ? 1 : 2
);
statusMessage.value = '解码完成,准备播放';
// 播放WAV文件
await player.setFilePath(wavFile.path);
await player.play();
isPlaying.value = true;
statusMessage.value = '正在播放解码后的音频';
// 监听播放完成
player.playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
isPlaying.value = false;
statusMessage.value = '播放完成';
}
});
} catch (e) {
statusMessage.value = '解码或播放失败: $e';
} finally {
isDecoding.value = false;
}
}
// 将PCM转换为WAV格式
Future<File> _convertPcmToWav(String pcmPath, String wavPath, int sampleRate, int channels) async {
final pcmFile = File(pcmPath);
final pcmData = await pcmFile.readAsBytes();
// 创建WAV文件 (44字节头部 + PCM数据)
final wavSize = 44 + pcmData.length;
final wavData = ByteData(wavSize);
// 写入WAV头部
_writeWavHeader(wavData, pcmData.length, sampleRate, channels);
// 写入PCM数据
for (int i = 0; i < pcmData.length; i++) {
wavData.setUint8(44 + i, pcmData[i]);
}
// 保存为WAV文件
final wavFile = File(wavPath);
await wavFile.writeAsBytes(wavData.buffer.asUint8List());
return wavFile;
}
// 写入WAV头部到ByteData
void _writeWavHeader(ByteData data, int pcmLength, int sampleRate, int channels) {
final bytesPerSample = 2; // 16位PCM
final byteRate = sampleRate * channels * bytesPerSample;
final blockAlign = channels * bytesPerSample;
// "RIFF"标识
data.setUint8(0, 0x52); // 'R'
data.setUint8(1, 0x49); // 'I'
data.setUint8(2, 0x46); // 'F'
data.setUint8(3, 0x46); // 'F'
// 文件大小 (减去8个字节的RIFF头部和大小字段)
data.setUint32(4, 36 + pcmLength, Endian.little);
// "WAVE"标识
data.setUint8(8, 0x57); // 'W'
data.setUint8(9, 0x41); // 'A'
data.setUint8(10, 0x56); // 'V'
data.setUint8(11, 0x45); // 'E'
// "fmt "子块
data.setUint8(12, 0x66); // 'f'
data.setUint8(13, 0x6D); // 'm'
data.setUint8(14, 0x74); // 't'
data.setUint8(15, 0x20); // ' '
// 子块大小 (16 for PCM)
data.setUint32(16, 16, Endian.little);
// 音频格式 (1 表示PCM)
data.setUint16(20, 1, Endian.little);
// 通道数量
data.setUint16(22, channels, Endian.little);
// 采样率
data.setUint32(24, sampleRate, Endian.little);
// 字节率 = 采样率 * 通道数 * 每样本字节数
data.setUint32(28, byteRate, Endian.little);
// 块对齐 = 通道数 * 每样本字节数
data.setUint16(32, blockAlign, Endian.little);
// 每个样本位数
data.setUint16(34, bytesPerSample * 8, Endian.little);
// "data"子块
data.setUint8(36, 0x64); // 'd'
data.setUint8(37, 0x61); // 'a'
data.setUint8(38, 0x74); // 't'
data.setUint8(39, 0x61); // 'a'
// 数据大小
data.setUint32(40, pcmLength, Endian.little);
}
}
// PCM音频源
class PCMAudioSource extends StreamAudioSource {
final Uint8List pcmData;
final int sampleRate;
final int channels;
PCMAudioSource({
required this.pcmData,
this.sampleRate = 16000,
this.channels = 1,
});
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
start = start ?? 0;
end = end ?? pcmData.length;
// 转换PCM到WAV格式
final wavHeader = _createWavHeader(pcmData.length, sampleRate, channels);
final wavData = Uint8List(wavHeader.length + pcmData.length);
wavData.setRange(0, wavHeader.length, wavHeader);
wavData.setRange(wavHeader.length, wavHeader.length + pcmData.length, pcmData);
// 调整请求范围来适应WAV头部
int adjustedStart = start;
int adjustedEnd = end;
if (start < wavHeader.length) {
adjustedStart = start;
} else {
adjustedStart = start + wavHeader.length;
}
if (end <= wavHeader.length) {
adjustedEnd = end;
} else {
adjustedEnd = end + wavHeader.length;
adjustedEnd = adjustedEnd > wavData.length ? wavData.length : adjustedEnd;
}
return StreamAudioResponse(
sourceLength: wavData.length,
contentLength: adjustedEnd - adjustedStart,
offset: adjustedStart,
stream: Stream.value(wavData.sublist(adjustedStart, adjustedEnd)),
contentType: 'audio/wav',
);
}
// 创建WAV头部
Uint8List _createWavHeader(int dataLength, int sampleRate, int channels) {
final bytesPerSample = 2; // 16位PCM
final byteRate = sampleRate * channels * bytesPerSample;
final blockAlign = channels * bytesPerSample;
final header = ByteData(44); // WAV头部固定44字节
// "RIFF"标识
header.setUint8(0, 0x52); // 'R'
header.setUint8(1, 0x49); // 'I'
header.setUint8(2, 0x46); // 'F'
header.setUint8(3, 0x46); // 'F'
// 文件大小 (减去8个字节的RIFF头部和大小字段)
header.setUint32(4, 36 + dataLength, Endian.little);
// "WAVE"标识
header.setUint8(8, 0x57); // 'W'
header.setUint8(9, 0x41); // 'A'
header.setUint8(10, 0x56); // 'V'
header.setUint8(11, 0x45); // 'E'
// "fmt "子块
header.setUint8(12, 0x66); // 'f'
header.setUint8(13, 0x6D); // 'm'
header.setUint8(14, 0x74); // 't'
header.setUint8(15, 0x20); // ' '
// 子块大小 (16 for PCM)
header.setUint32(16, 16, Endian.little);
// 音频格式 (1 表示PCM)
header.setUint16(20, 1, Endian.little);
// 通道数量
header.setUint16(22, channels, Endian.little);
// 采样率
header.setUint32(24, sampleRate, Endian.little);
// 字节率 = 采样率 * 通道数 * 每样本字节数
header.setUint32(28, byteRate, Endian.little);
// 块对齐 = 通道数 * 每样本字节数
header.setUint16(32, blockAlign, Endian.little);
// 每个样本位数
header.setUint16(34, bytesPerSample * 8, Endian.little);
// "data"子块
header.setUint8(36, 0x64); // 'd'
header.setUint8(37, 0x61); // 'a'
header.setUint8(38, 0x74); // 't'
header.setUint8(39, 0x61); // 'a'
// 数据大小
header.setUint32(40, dataLength, Endian.little);
return header.buffer.asUint8List();
}
}

445
lib/modules/opus_test/views/opus_test_view.dart

@ -1,445 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/opus_test_controller.dart';
class OpusTestView extends GetView<OpusTestController> {
const OpusTestView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 获取主题模式
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
title: Text(
'OPUS解码',
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
centerTitle: true,
bottom: TabBar(
indicatorColor: Colors.blue,
indicatorWeight: 3,
labelColor: isDarkMode ? Colors.white : Colors.black,
unselectedLabelColor: isDarkMode ? Colors.grey : Colors.grey,
tabs: const [
Tab(text: "解码"),
Tab(text: "输出"),
],
),
),
body: TabBarView(
children: [
_buildDecodeTab(isDarkMode),
_buildEncodeTab(isDarkMode),
],
),
),
);
}
// 解码标签页
Widget _buildDecodeTab(bool isDarkMode) {
return ListView(
padding: EdgeInsets.all(16.w),
children: [
// 文件选择部分
_buildSection(
title: '选择文件',
isDarkMode: isDarkMode,
child: Column(
children: [
Obx(() => controller.selectedFiles.isEmpty
? const SizedBox.shrink()
: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: controller.selectedFiles.length,
itemBuilder: (context, index) {
final file = controller.selectedFiles[index];
return ListTile(
title: Text(
file.name,
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
subtitle: Text(
'${(file.size / 1024).toStringAsFixed(2)} Kb',
style: TextStyle(
color: isDarkMode ? Colors.white38 : Colors.black54,
fontSize: 12.sp,
),
),
trailing: Radio<int>(
value: index,
groupValue: 0,
onChanged: (value) {},
activeColor: Colors.blue,
),
);
},
)),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
Icons.refresh,
color: isDarkMode ? Colors.blue[300] : Colors.blue,
),
onPressed: () {
controller.selectedFiles.clear();
},
),
],
),
],
),
),
SizedBox(height: 16.h),
// 解码参数部分
_buildSection(
title: '解码参数',
isDarkMode: isDarkMode,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Obx(() => Checkbox(
value: controller.hasHeader.value,
onChanged: (value) => controller.toggleHeader(value!),
activeColor: Colors.blue,
)),
Text(
'是否携带协议头',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
],
),
// 数据包长度
Row(
children: [
Text(
'数据包长度',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
SizedBox(width: 16.w),
Expanded(
child: Obx(() => TextField(
controller: TextEditingController(
text: controller.packetLength.value),
onChanged: (value) => controller.setPacketLength(value),
keyboardType: TextInputType.number,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
),
decoration: InputDecoration(
isDense: true,
suffix: Text(
'Byte',
style: TextStyle(
color: isDarkMode
? Colors.white60
: Colors.black54,
fontSize: 12.sp,
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: isDarkMode
? Colors.white30
: Colors.black26,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
),
)),
),
],
),
SizedBox(height: 20.h),
// 采样率
Row(
children: [
Text(
'采样率',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
SizedBox(width: 16.w),
Expanded(
child: Obx(() => DropdownButton<int>(
value: controller.selectedSampleRate.value,
isExpanded: true,
dropdownColor:
isDarkMode ? Colors.grey[800] : Colors.white,
items: controller.sampleRateOptions.map((rate) {
return DropdownMenuItem<int>(
value: rate,
child: Text(
rate.toString(),
style: TextStyle(
color: isDarkMode
? Colors.white
: Colors.black87,
),
),
);
}).toList(),
onChanged: (value) =>
controller.setSampleRate(value!),
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
),
)),
),
],
),
SizedBox(height: 20.h),
// 通道数量
Text(
'通道数量',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
Row(
children: [
Expanded(
child: Row(
children: [
Obx(() => Radio<bool>(
value: true,
groupValue: controller.isMono.value,
onChanged: (value) =>
controller.toggleChannel(value!),
activeColor: Colors.blue,
)),
Text(
'单通道',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
],
),
),
Expanded(
child: Row(
children: [
Obx(() => Radio<bool>(
value: false,
groupValue: controller.isMono.value,
onChanged: (value) =>
controller.toggleChannel(value!),
activeColor: Colors.blue,
)),
Text(
'双通道',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 14.sp,
),
),
],
),
),
],
),
],
),
),
SizedBox(height: 16.h),
// 状态消息
Obx(() => controller.statusMessage.value.isNotEmpty
? Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration(
color: isDarkMode
? Colors.blue.withOpacity(0.2)
: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.r),
border: Border.all(
color: isDarkMode
? Colors.blue.withOpacity(0.3)
: Colors.blue.withOpacity(0.2),
width: 1,
),
),
child: Text(
controller.statusMessage.value,
style: TextStyle(
color: isDarkMode ? Colors.blue[300] : Colors.blue[700],
fontSize: 12.sp,
),
),
)
: const SizedBox.shrink()),
// 播放按钮
Container(
margin: EdgeInsets.symmetric(vertical: 8.h),
child: ElevatedButton.icon(
icon: Obx(() => Icon(
controller.isPlaying.value
? Icons.stop
: Icons.play_arrow,
color: Colors.white,
)),
label: Obx(() => Text(
controller.isPlaying.value ? '停止播放' : '播放音频',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
onPressed: controller.isDecoding.value
? null
: () {
controller.playAudio();
},
),
),
// 解码按钮
Container(
margin: EdgeInsets.symmetric(vertical: 8.h),
child: Obx(() => ElevatedButton(
onPressed: controller.selectedFiles.isEmpty ||
controller.isDecoding.value
? null
: () {
controller.pickFiles();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
child: Text(
'开始解码',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
)),
),
],
);
}
// 编码标签页(暂未实现)
Widget _buildEncodeTab(bool isDarkMode) {
return Center(
child: Text(
'编码功能尚未实现',
style: TextStyle(
color: isDarkMode ? Colors.white54 : Colors.black54,
fontSize: 16.sp,
),
),
);
}
// 构建带标题的区段
Widget _buildSection({
required String title,
required Widget child,
required bool isDarkMode,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
SizedBox(width: 8.w),
Expanded(
child: Container(
height: 1,
color: isDarkMode ? Colors.white24 : Colors.black12,
),
),
if (title == '选择文件')
IconButton(
icon: Icon(
Icons.add_circle_outline,
color: isDarkMode ? Colors.blue[300] : Colors.blue,
),
onPressed: () {
controller.pickFiles();
},
),
],
),
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(8.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.03),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: child,
),
],
);
}
}

114
lib/modules/pairing/controllers/pairing_controller.dart

@ -112,24 +112,24 @@ class PairingController extends GetxController {
statusDescription.value = 'checkingBluetoothStatus'.tr; statusDescription.value = 'checkingBluetoothStatus'.tr;
// 无论是iOS还是Android,都正常检查蓝牙状态 // 无论是iOS还是Android,都正常检查蓝牙状态
try { //try {
final isEnabled = await _bluetoothService.isBluetoothEnabled(); // final isEnabled = await _bluetoothService.isBluetoothEnabled();
_bluetoothState.value = isEnabled ? 'on' : 'off'; // _bluetoothState.value = isEnabled ? 'on' : 'off';
if (isEnabled) { // if (isEnabled) {
// 如果蓝牙已开启,刷新设备连接 // // 如果蓝牙已开启,刷新设备连接
await _bluetoothService.refreshDeviceConnection(); // await _bluetoothService.refreshDeviceConnection();
} // }
} catch (e) { // } catch (e) {
Logger.e(_tag, '检查蓝牙状态失败: ${e.toString()}'); // Logger.e(_tag, '检查蓝牙状态失败: ${e.toString()}');
_bluetoothState.value = 'off'; // _bluetoothState.value = 'off';
} // }
if (_bluetoothState.value != 'on') { // if (_bluetoothState.value != 'on') {
statusDescription.value = 'bluetoothTurnedOff'.tr; // statusDescription.value = 'bluetoothTurnedOff'.tr;
actionButtonText.value = '去系统设置'; // actionButtonText.value = '去系统设置';
return; // return;
} // }
// 刷新连接设备并检查配对状态 // 刷新连接设备并检查配对状态
statusDescription.value = 'searchingDevice'.tr; statusDescription.value = 'searchingDevice'.tr;
@ -146,6 +146,47 @@ class PairingController extends GetxController {
if (!isDeviceConnected) return; if (!isDeviceConnected) return;
try { try {
// iOS 环境下的配对处理
if (Platform.isIOS) {
Logger.i(_tag, 'iOS: 检查设备配对状态');
// 判断设备名称是否符合要求
if (!_isValidDeviceName(currentDevice!.name)) {
statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr;
return;
}
// 经典蓝牙连接成功后,主动触发配对流程
Logger.i(_tag, 'iOS环境下主动启动配对流程: ${currentDevice!.name}');
// 更新UI状态
isLoading.value = true;
statusDescription.value = '正在启动配对流程...';
// 调用BleService的startDeviceAssociation方法
final success = await _bleService.startDeviceAssociation(
deviceNamePattern: currentDevice!.name,
macAddress: currentDevice!.address,
);
isLoading.value = false;
if (success) {
Logger.i(_tag, 'iOS配对流程启动成功');
_companionAssociated.value = true;
_userRejectedAssociation.value = false; // 重置拒绝状态
_autoNavigateToHome();
} else {
Logger.w(_tag, 'iOS配对流程启动失败,但仍继续使用');
// 即使配对流程启动失败,也视为已配对以继续流程
_companionAssociated.value = true;
_userRejectedAssociation.value = false;
_autoNavigateToHome();
}
return;
}
// Android环境下的正常配对检查流程
final isSupported = await _bleService.isCompanionSupported(); final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) { if (!isSupported) {
Logger.i(_tag, '设备不支持Companion功能'); Logger.i(_tag, '设备不支持Companion功能');
@ -364,45 +405,10 @@ class PairingController extends GetxController {
_updateStatusDescription(); _updateStatusDescription();
try { try {
// iOS 环境也启用完整的关联流程 // iOS环境下不在这里处理关联流程,已在_checkDevicePairingStatus中处理
if (Platform.isIOS) { if (Platform.isIOS) {
Logger.i(_tag, 'iOS: 启动完整关联流程,使用AccessorySetupKit'); Logger.i(_tag, 'iOS: 在_checkDevicePairingStatus中已处理关联流程');
// 检查是否支持Companion功能
final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) {
Logger.i(_tag, 'iOS设备不支持Companion功能,跳过关联');
_companionAssociated.value = true;
_autoNavigateToHome();
return;
}
// 检查设备名称是否符合要求
if (!_isValidDeviceName(currentDevice!.name)) {
statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr;
_isAssociating = false;
return;
}
Logger.i(_tag, 'iOS开始Companion设备关联流程, 设备: ${currentDevice!.name}');
var success = await _bleService.startDeviceAssociation(
deviceNamePattern: currentDevice!.name,
macAddress: currentDevice!.address,
);
_isAssociating = false; _isAssociating = false;
if (success) {
Logger.i(_tag, 'iOS Companion设备关联成功');
_companionAssociated.value = true;
_userRejectedAssociation.value = false;
_autoNavigateToHome();
} else {
Logger.e(_tag, 'iOS Companion设备关联失败或被用户拒绝');
_userRejectedAssociation.value = true;
_updateStatusDescription();
}
return; return;
} }

24
lib/modules/settings/views/settings_view.dart

@ -503,29 +503,7 @@ class SettingsView extends GetView<SettingsController> {
? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), : Colors.grey[200]),
// OPUS测试
_buildSimpleNavigationSetting(
title: 'OPUS解码测试',
subtitle: '测试OPUS音频文件的解码和播放',
icon: Icons.audio_file,
iconBgColor: isDarkMode
? Colors.purple[900]!.withOpacity(0.3)
: Colors.purple[100]!,
iconColor:
isDarkMode ? Colors.purple[300]! : Colors.purple[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测试 // BLE测试
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
title: 'BLE蓝牙测试', title: 'BLE蓝牙测试',

10
lib/routes/app_pages.dart

@ -30,8 +30,6 @@ import './app_routes.dart';
import '../modules/settings/views/settings_view.dart'; import '../modules/settings/views/settings_view.dart';
import '../modules/settings/views/permissions_view.dart'; import '../modules/settings/views/permissions_view.dart';
import '../modules/settings/bindings/settings_binding.dart'; import '../modules/settings/bindings/settings_binding.dart';
import '../modules/opus_test/views/opus_test_view.dart';
import '../modules/opus_test/bindings/opus_test_binding.dart';
import '../modules/speech_test/views/speech_test_view.dart'; import '../modules/speech_test/views/speech_test_view.dart';
import '../modules/speech_test/bindings/speech_test_binding.dart'; import '../modules/speech_test/bindings/speech_test_binding.dart';
import '../modules/agent/views/agent_view.dart'; import '../modules/agent/views/agent_view.dart';
@ -161,13 +159,7 @@ abstract class AppPages {
transition: Transition.rightToLeft, transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250), transitionDuration: Duration(milliseconds: 250),
), ),
GetPage(
name: Routes.opusTest,
page: () => const OpusTestView(),
binding: OpusTestBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
GetPage( GetPage(
name: Routes.speechTest, name: Routes.speechTest,
page: () => const SpeechTestView(), page: () => const SpeechTestView(),

864
local_plugins/agent_service/ios/Classes/AgentServiceImpl.swift

File diff suppressed because it is too large

2
local_plugins/agent_service/ios/Classes/AgentServicePlugin.h

@ -1,4 +1,4 @@
#import <Flutter/Flutter.h> #import <Flutter/Flutter.h>
@interface AgentServicePlugin : NSObject<FlutterPlugin> @interface AgentServicePlugin : NSObject<FlutterPlugin>
@end @end

303
local_plugins/agent_service/ios/Classes/AgentServicePlugin.swift

@ -1,227 +1,160 @@
import Flutter import Flutter
import UIKit import UIKit
import AVFoundation import Foundation
import os.log
/// 代理服务插件,负责连接Flutter端与原生服务 /// 代理服务Flutter插件,负责将Flutter调用桥接到原生实现
public class AgentServicePlugin: NSObject, FlutterPlugin { public class AgentServicePlugin: NSObject, FlutterPlugin {
private let TAG = "AgentServicePlugin"
private var eventSink: FlutterEventSink?
// MARK: - 插件注册
/// 插件注册方法 /// 插件注册方法,Flutter引擎调用
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let methodChannel = FlutterMethodChannel(name: "com.yunqiinnovation.agent_service", binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: "com.yunqiinnovation.agent_service", binaryMessenger: registrar.messenger())
let eventChannel = FlutterEventChannel(name: "com.yunqiinnovation.agent_service/events", binaryMessenger: registrar.messenger()) let eventChannel = FlutterEventChannel(name: "com.yunqiinnovation.agent_service/events", binaryMessenger: registrar.messenger())
let instance = AgentServicePlugin() let instance = AgentServicePlugin()
registrar.addMethodCallDelegate(instance, channel: methodChannel) registrar.addMethodCallDelegate(instance, channel: channel)
eventChannel.setStreamHandler(instance) eventChannel.setStreamHandler(instance.eventHandler)
// 添加监听器
AgentServiceImpl.shared.addListener(instance)
NSLog("\(instance.TAG): 插件已加载")
} }
// MARK: - 方法处理 // MARK: - 属性
/// 处理来自Flutter的方法调用 private let impl = AgentServiceImpl.shared
private lazy var eventHandler = AgentServiceEventHandler(impl: impl)
// MARK: - FlutterPlugin 实现
/// 处理Flutter发来的方法调用
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
NSLog("\(TAG): 收到方法调用 - \(call.method)")
switch call.method { switch call.method {
case "initialize":
handleInitialize(call, result: result)
case "startAgentService": case "startAgentService":
handleStartAgentService(call, result: result) guard let arguments = call.arguments as? [String: Any],
let config = arguments["config"] as? [String: Any] else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "配置参数不能为空", details: nil))
return
}
let success = impl.initialize(config: config)
result(success)
case "stopAgentService": case "stopAgentService":
handleStopAgentService(result: result) let success = impl.dispose()
result(success)
case "startConversation": case "startConversation":
handleStartConversation(result: result) impl.startRecognition()
result(true)
case "stopConversation": case "stopConversation":
handleStopConversation(result: result) impl.stopRecognition()
result(true)
case "processTextInput": case "processTextInput":
handleProcessTextInput(call, result: result) guard let arguments = call.arguments as? [String: Any],
let text = arguments["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let speakResponse = arguments["speakResponse"] as? Bool ?? false
let success = impl.processTextInput(text, speakResponse: speakResponse)
result(success)
case "processImageInput":
guard let arguments = call.arguments as? [String: Any],
let imagePath = arguments["imagePath"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "图片路径不能为空", details: nil))
return
}
let text = arguments["text"] as? String ?? ""
let speakResponse = arguments["speakResponse"] as? Bool ?? false
let success = impl.processImageInput(imagePath: imagePath, text: text, speakResponse: speakResponse)
result(success)
case "speakText": case "speakText":
handleSpeakText(call, result: result) guard let arguments = call.arguments as? [String: Any],
let text = arguments["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = impl.speakText(text)
result(success)
case "stopTts": case "stopTts":
handleStopTts(result: result) let success = impl.stopTts()
result(success)
case "clearChatHistory": case "clearChatHistory":
handleClearChatHistory(result: result) let success = impl.clearChatHistory()
result(success)
case "interruptCurrentResponse": case "interruptCurrentResponse":
handleInterruptCurrentResponse(result: result) let success = impl.interruptCurrentResponse()
case "processImageInput": result(success)
handleProcessImageInput(call, result: result)
case "dispose": case "dispose":
handleDispose(result: result) let success = impl.dispose()
result(success)
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }
} }
}
/// 代理服务事件处理器,负责将原生事件传递给Flutter
class AgentServiceEventHandler: NSObject, FlutterStreamHandler {
private let impl: AgentServiceImpl
private var eventSink: FlutterEventSink?
private let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceEventHandler")
// MARK: - 方法实现 init(impl: AgentServiceImpl) {
self.impl = impl
private func handleInitialize(_ call: FlutterMethodCall, result: @escaping FlutterResult) { super.init()
guard let args = call.arguments as? [String: Any],
let config = args["config"] as? [String: Any] else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "参数无效", details: nil))
return
}
// 初始化AgentServiceImpl
let success = AgentServiceImpl.shared.initialize(config: config)
result(success)
}
private func handleStartAgentService(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let config = args["config"] as? [String: Any] else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "参数无效", details: nil))
return
}
NSLog("\(TAG): 启动AgentService,配置参数: \(config)")
// 初始化AgentServiceImpl
let success = AgentServiceImpl.shared.initialize(config: config)
if success {
result(true)
} else {
result(FlutterError(code: "SERVICE_START_ERROR", message: "AgentService启动失败", details: nil))
}
}
private func handleStopAgentService(result: @escaping FlutterResult) {
NSLog("\(TAG): 停止AgentService")
let success = AgentServiceImpl.shared.dispose()
result(success)
}
private func handleStartConversation(result: @escaping FlutterResult) {
NSLog("\(TAG): 开始对话")
let success = AgentServiceImpl.shared.startRecognition()
result(success)
}
private func handleStopConversation(result: @escaping FlutterResult) {
NSLog("\(TAG): 停止对话")
let success = AgentServiceImpl.shared.stopRecognition()
result(success)
}
private func handleProcessTextInput(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let text = args["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let speakResponse = args["speakResponse"] as? Bool ?? false
NSLog("\(TAG): 处理文本输入 - \(text)")
let success = AgentServiceImpl.shared.processTextInput(text, speakResponse: speakResponse)
result(success)
}
private func handleSpeakText(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let text = args["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
NSLog("\(TAG): 语音合成 - \(text)")
let success = AgentServiceImpl.shared.speakText(text)
result(success)
}
private func handleStopTts(result: @escaping FlutterResult) {
NSLog("\(TAG): 停止语音合成")
let success = AgentServiceImpl.shared.stopTts()
result(success)
}
private func handleClearChatHistory(result: @escaping FlutterResult) {
NSLog("\(TAG): 清除聊天历史")
let success = AgentServiceImpl.shared.clearChatHistory()
result(success)
}
private func handleInterruptCurrentResponse(result: @escaping FlutterResult) {
NSLog("\(TAG): 中断当前响应")
let success = AgentServiceImpl.shared.interruptCurrentResponse()
result(success)
}
private func handleProcessImageInput(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let imagePath = args["imagePath"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "图片路径不能为空", details: nil))
return
}
let text = args["text"] as? String ?? ""
let speakResponse = args["speakResponse"] as? Bool ?? false
NSLog("\(TAG): 处理图片输入 - \(imagePath)")
let success = AgentServiceImpl.shared.processImageInput(imagePath: imagePath, text: text, speakResponse: speakResponse)
result(success)
} }
private func handleDispose(result: @escaping FlutterResult) { /// 监听开始时调用
NSLog("\(TAG): 释放资源") func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
// 移除监听器并释放资源
AgentServiceImpl.shared.removeListener(self)
let success = AgentServiceImpl.shared.dispose()
result(success) // 添加实现的事件监听器
impl.addListener(self)
return nil
} }
// MARK: - 实例析构 /// 监听取消时调用
func onCancel(withArguments arguments: Any?) -> FlutterError? {
deinit { impl.removeListener(self)
// 确保在实例销毁时移除监听器 self.eventSink = nil
AgentServiceImpl.shared.removeListener(self) return nil
NSLog("\(TAG): 插件已卸载")
} }
} }
// MARK: - AgentServiceListener 实现 // 扩展事件处理器以实现AgentServiceListener接口
extension AgentServicePlugin: AgentServiceListener { extension AgentServiceEventHandler: AgentServiceListener {
public func onEvent(eventName: String, data: [String: Any]) { func onEvent(eventName: String, data: [String: Any]) {
guard let eventSink = eventSink else { return } // 确保在主线程上调用
DispatchQueue.main.async { [weak self] in
// 将事件转换为JSON字符串 guard let self = self else { return }
let eventData: [String: Any] = [
"event": eventName, // 将事件数据格式化为与Android版本一致的格式
"data": data let eventData: [String: Any] = [
] "event": eventName,
"data": data
do { ]
let jsonData = try JSONSerialization.data(withJSONObject: eventData, options: [])
if let jsonString = String(data: jsonData, encoding: .utf8) { // 转换为JSON字符串发送,与Android版本保持一致
// 确保在主线程发送事件到Flutter do {
DispatchQueue.main.async { let jsonData = try JSONSerialization.data(withJSONObject: eventData, options: [])
eventSink(jsonString) if let jsonString = String(data: jsonData, encoding: .utf8) {
self.eventSink?(jsonString)
} else {
os_log("无法创建JSON字符串", log: self.logger, type: .error)
} }
} catch {
os_log("JSON序列化失败: %{public}@", log: self.logger, type: .error, error.localizedDescription)
} }
} catch {
NSLog("\(TAG): 错误 - 事件数据序列化失败: \(error.localizedDescription)")
} }
} }
}
// MARK: - FlutterStreamHandler 实现
extension AgentServicePlugin: FlutterStreamHandler {
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}
} }

240
local_plugins/agent_service/ios/Classes/AudioSessionHub.swift

@ -0,0 +1,240 @@
import AVFoundation
import os.log
public enum AudioIntent {
case voice
case playback
}
public class AudioSessionHub {
public static let shared = AudioSessionHub()
private let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AudioSessionHub")
private var currentIntent: AudioIntent?
private let audioSession = AVAudioSession.sharedInstance()
private var isInterrupted = false
private init() {
setupNotifications()
}
public func begin(_ intent: AudioIntent) {
if intent == currentIntent && audioSession.isOtherAudioPlaying == false {
return
}
currentIntent = intent
do {
switch intent {
case .voice:
try audioSession.setCategory(.playAndRecord,
mode: .voiceChat,
options: [.allowBluetooth, .mixWithOthers])
// 如果有蓝牙HFP设备,强制设置为首选输入
if let bluetoothInput = audioSession.availableInputs?.first(where: { $0.portType == .bluetoothHFP }) {
try audioSession.setPreferredInput(bluetoothInput)
os_log("已设置蓝牙HFP为首选输入: %{public}@", log: logger, type: .info, bluetoothInput.portName)
}
case .playback:
try audioSession.setCategory(.playback,
mode: .default,
options: [.allowBluetooth, .mixWithOthers])
}
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
os_log("音频会话已设置为:%{public}@", log: logger, type: .debug, String(describing: intent))
// 输出当前音频路由信息
let hasBluetoothDevice = hasBluetoothAudioDevice()
let hasBluetoothMic = hasBluetoothMicrophone()
let currentInput = getCurrentInputDevice()
os_log("蓝牙音频设备状态: %{public}@", log: logger, type: .info, hasBluetoothDevice ? "已连接" : "未连接")
os_log("蓝牙麦克风状态: %{public}@", log: logger, type: .info, hasBluetoothMic ? "已连接" : "未连接")
os_log("当前输入设备: %{public}@", log: logger, type: .info, currentInput)
if hasBluetoothDevice {
os_log("%{public}@", log: logger, type: .debug, getCurrentAudioRoute())
}
} catch {
os_log("设置音频会话失败:%{public}@", log: logger, type: .error, error.localizedDescription)
}
}
public func appWillEnterForeground() {
if let intent = currentIntent {
begin(intent)
}
}
public func appDidEnterBackground() {
do {
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
os_log("音频会话已释放(应用进入后台)", log: logger, type: .debug)
} catch {
os_log("释放音频会话失败:%{public}@", log: logger, type: .error, error.localizedDescription)
}
}
/// 获取当前音频路由信息
public func getCurrentAudioRoute() -> String {
let currentRoute = audioSession.currentRoute
var routeInfo = "当前音频路由:\n"
// 输出设备
routeInfo += "输出设备:\n"
for output in currentRoute.outputs {
routeInfo += " - \(output.portName) (\(output.portType.rawValue))\n"
}
// 输入设备
routeInfo += "输入设备:\n"
for input in currentRoute.inputs {
routeInfo += " - \(input.portName) (\(input.portType.rawValue))\n"
}
return routeInfo
}
/// 检查是否有蓝牙音频设备连接
public func hasBluetoothAudioDevice() -> Bool {
let currentRoute = audioSession.currentRoute
// 检查输出设备
for output in currentRoute.outputs {
if output.portType == .bluetoothA2DP ||
output.portType == .bluetoothHFP ||
output.portType == .bluetoothLE {
return true
}
}
// 检查输入设备(这是关键!)
for input in currentRoute.inputs {
if input.portType == .bluetoothHFP {
return true
}
}
return false
}
/// 检查是否有蓝牙麦克风作为输入设备
public func hasBluetoothMicrophone() -> Bool {
let currentRoute = audioSession.currentRoute
for input in currentRoute.inputs {
if input.portType == .bluetoothHFP {
return true
}
}
return false
}
/// 获取当前输入设备信息
public func getCurrentInputDevice() -> String {
let currentRoute = audioSession.currentRoute
if let input = currentRoute.inputs.first {
return "\(input.portName) (\(input.portType.rawValue))"
}
return "无输入设备"
}
private func setupNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAudioSessionInterruption),
name: AVAudioSession.interruptionNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAudioRouteChange),
name: AVAudioSession.routeChangeNotification,
object: nil
)
}
/// 处理音频中断
@objc private func handleAudioSessionInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
switch type {
case .began:
// 中断开始
isInterrupted = true
os_log("音频会话被中断", log: logger, type: .debug)
case .ended:
// 中断结束
isInterrupted = false
// 检查是否应该恢复
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume), let intent = currentIntent {
// 恢复之前的音频意图
begin(intent)
os_log("音频会话中断结束,已恢复", log: logger, type: .debug)
}
}
@unknown default:
os_log("未知的音频中断类型: %{public}@", log: logger, type: .error, "\(type)")
}
}
/// 处理音频路由变化
@objc private func handleAudioRouteChange(notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
return
}
switch reason {
case .newDeviceAvailable:
os_log("新音频设备已连接", log: logger, type: .info)
os_log("%{public}@", log: logger, type: .debug, getCurrentAudioRoute())
// 重新应用当前意图以确保正确路由
if let intent = currentIntent {
begin(intent)
}
case .oldDeviceUnavailable:
os_log("音频设备已断开", log: logger, type: .info)
os_log("%{public}@", log: logger, type: .debug, getCurrentAudioRoute())
// 重新应用当前意图以确保正确路由
if let intent = currentIntent {
begin(intent)
}
case .categoryChange:
os_log("音频类别已变化", log: logger, type: .info)
default:
break
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}

BIN
local_plugins/agent_service/ios/Resources/calling.mp3

Binary file not shown.

BIN
local_plugins/agent_service/ios/Resources/start.mp3

Binary file not shown.

BIN
local_plugins/agent_service/ios/Resources/stop.mp3

Binary file not shown.

15
local_plugins/agent_service/ios/agent_service.podspec

@ -5,21 +5,24 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'agent_service' s.name = 'agent_service'
s.version = '0.0.1' s.version = '0.0.1'
s.summary = 'A Flutter plugin for agent service with voice interaction capabilities.' s.summary = '智能助手代理服务'
s.description = <<-DESC s.description = <<-DESC
A Flutter plugin that provides voice interaction, AI conversation and text-to-speech functionalities. 智能助手代理服务,提供语音识别、语音合成和OpenAI对话功能
DESC DESC
s.homepage = 'http://example.com' s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' } s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*.swift'
s.resources = ['Resources/*.mp3']
s.dependency 'Flutter' s.dependency 'Flutter'
s.dependency 'azure_speech' s.dependency 'azure_speech'
s.dependency 'ble_service' s.dependency 'ble_service'
s.platform = :ios, '11.0' s.dependency 'open_ai_service'
s.platform = :ios, '13.0'
# Flutter.framework does not contain a i386 slice. # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
s.swift_version = '5.0' s.swift_version = '5.0'
end end

1
local_plugins/agent_service/lib/agent_service.dart

@ -106,7 +106,6 @@ class AgentService {
final Map<String, dynamic> eventMap = jsonDecode(event); final Map<String, dynamic> eventMap = jsonDecode(event);
final String eventName = eventMap['event']; final String eventName = eventMap['event'];
final Map<String, dynamic> eventData = eventMap['data']; final Map<String, dynamic> eventData = eventMap['data'];
// 将事件名称转换为枚举 // 将事件名称转换为枚举
final AgentServiceEventType type = _stringToEventType(eventName); final AgentServiceEventType type = _stringToEventType(eventName);

13
local_plugins/agent_service/pubspec.yaml

@ -10,6 +10,15 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# 本地插件依赖
open_ai_service:
path: ../open_ai_service
azure_speech:
path: ../azure_speech
ble_service:
path: ../ble_service
speech:
path: ../speech
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -22,4 +31,6 @@ flutter:
platforms: platforms:
android: android:
package: com.yunqiinnovation.agent_service package: com.yunqiinnovation.agent_service
pluginClass: AgentServicePlugin pluginClass: AgentServicePlugin
ios:
pluginClass: AgentServicePlugin

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

@ -278,7 +278,7 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
private fun generateSsml(rawText: String): String { private fun generateSsml(rawText: String): String {
// 1. 定义要静音的符号和表情符号列表 // 1. 定义要静音的符号和表情符号列表
val symbolsToMute = listOf( val symbolsToMute = listOf(
"#", "*", "@", "%", "^", "&", "#", "*",
"😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉" "😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉"
) )

673
local_plugins/azure_speech/ios/Classes/AzureAsrHelper.swift

@ -3,6 +3,8 @@ import AVFoundation
import MicrosoftCognitiveServicesSpeech import MicrosoftCognitiveServicesSpeech
// 自定义语音处理组件,提供音频流处理等功能 // 自定义语音处理组件,提供音频流处理等功能
import speech import speech
import os.log
/** /**
* Azure ASR Helper * Azure ASR Helper
@ -10,8 +12,10 @@ import speech
* 基于微软Azure语音服务的ASR实现 * 基于微软Azure语音服务的ASR实现
* 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-recognize-speech * 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-recognize-speech
*/ */
class AzureAsrHelper: NSObject, IAsrService { public class AzureAsrHelper: NSObject {
private let tag = "AzureAsrHelper" private let tag = "AzureAsrHelper"
// 日志对象
private let log = OSLog(subsystem: "com.azure.speech", category: "AzureAsrHelper")
// 核心组件 // 核心组件
private var speechConfig: SPXSpeechConfiguration? private var speechConfig: SPXSpeechConfiguration?
@ -19,15 +23,17 @@ class AzureAsrHelper: NSObject, IAsrService {
private var audioConfig: SPXAudioConfiguration? private var audioConfig: SPXAudioConfiguration?
// 状态管理 // 状态管理
private var isContinuousRecognitionActive = false private var _isContinuousRecognitionActive = false
// 配置参数 // 配置参数
private var currentLanguage = "zh-CN" private var currentLanguage = "zh-CN"
private var supportedLanguages = ["zh-CN"] private var supportedLanguages = ["zh-CN"]
private var isAutoDetectLanguage = false private var isAutoDetectLanguage = false
private var subscriptionKey = ""
private var region = ""
// 音频源配置 // 音频源配置
private enum AudioSourceType { public enum AudioSourceType {
/** 使用设备麦克风 */ /** 使用设备麦克风 */
case microphone case microphone
@ -38,44 +44,49 @@ class AzureAsrHelper: NSObject, IAsrService {
private var audioSourceType = AudioSourceType.microphone private var audioSourceType = AudioSourceType.microphone
// 音频处理 // 音频处理
private var externalAudioStream: ExternalAudioStream? private var externalAudioStream: ExternalAudioPullStream?
// 事件监听处理
private var eventListeners = NSHashTable<AnyObject>.weakObjects()
// 音频会话
private let audioSession = AVAudioSession.sharedInstance()
/** /**
* 初始化语音识别服务 * 初始化Azure语音服务
* *
* @param subscriptionKey 服务订阅密钥/访问令牌 * @param subscriptionKey Azure 订阅密钥
* @param region 服务区域/应用ID * @param region Azure 区域
* @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"] * @param supportedLanguages 支持的语言数组
* @return 是否初始化成功 * @param audioSourceType 音频源类型
* @return 初始化是否成功
*/ */
func initialize(subscriptionKey: String, region: String, supportedLanguages: [String]?) -> Bool { public func initialize(
subscriptionKey: String,
region: String,
supportedLanguages: [String] = ["zh-CN"],
audioSourceType: AudioSourceType = .microphone
) -> Bool {
do { do {
// 检查配置是否为空 // 检查配置是否为空
if subscriptionKey.isEmpty || region.isEmpty { if subscriptionKey.isEmpty || region.isEmpty {
print("\(tag): Azure 配置信息不完整") os_log("Azure 配置信息不完整", log: log, type: .error)
return false return false
} }
// 释放之前的资源 // 释放之前的资源
dispose() dispose()
// 保存配置
self.subscriptionKey = subscriptionKey
self.region = region
self.audioSourceType = audioSourceType
// 设置语言 // 设置语言
if let supportedLanguages = supportedLanguages, !supportedLanguages.isEmpty { if !supportedLanguages.isEmpty {
self.supportedLanguages = supportedLanguages self.supportedLanguages = supportedLanguages
} }
// 根据支持的语言数量决定是否启用自动语言检测 // 根据支持的语言数量决定是否启用自动语言检测
self.isAutoDetectLanguage = self.supportedLanguages.count >= 2 self.isAutoDetectLanguage = supportedLanguages.count >= 2
// 如果只有一种语言,设置为当前语言 // 如果只有一种语言,设置为当前语言
if !isAutoDetectLanguage && !self.supportedLanguages.isEmpty { if !isAutoDetectLanguage && !supportedLanguages.isEmpty {
self.currentLanguage = self.supportedLanguages[0] self.currentLanguage = supportedLanguages[0]
} }
// 创建语音配置 // 创建语音配置
@ -84,8 +95,7 @@ class AzureAsrHelper: NSObject, IAsrService {
if isAutoDetectLanguage { if isAutoDetectLanguage {
// 直接启用语言检测模式 // 直接启用语言检测模式
speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode")
print("AzureAsrHelper: 启用语言检测模式") os_log("启用语言检测模式", log: log, type: .info)
} else { } else {
// 设置指定的识别语言 // 设置指定的识别语言
speechConfig?.speechRecognitionLanguage = currentLanguage speechConfig?.speechRecognitionLanguage = currentLanguage
@ -94,363 +104,347 @@ class AzureAsrHelper: NSObject, IAsrService {
// 创建识别器 // 创建识别器
return setupRecognizer() return setupRecognizer()
} catch { } catch {
print("\(tag): 初始化失败: \(error.localizedDescription)") os_log("初始化失败: %{public}@", log: log, type: .error, error.localizedDescription)
return false return false
} }
} }
/** /**
* 单次识别(非流式) * 向音频流写入音频数据
* 仅当音频源设置为external时有效
* *
* @param audioData 要识别的音频数据字节数组 * @param data 音频数据字节数组
* @param callback 识别结果回调
* @return 是否成功开始识别
*/ */
func recognizeOnce(audioData: Data, callback: @escaping (String) -> Void) -> Bool { public func pushAudioData(data: Data) {
return false if audioSourceType != .external {
return
}
// 使用拉流模式,将数据推入队列
externalAudioStream?.pushAudio(data)
} }
/** /**
* 启动连续识别 * 开始连续语音识别
* *
* @return 是否成功启动 * @param callback 连续识别结果回调
* @param audioSourceType 音频源类型
* @return 是否成功开始识别
*/ */
func startContinuousRecognition() -> Bool { public func startContinuousRecognition(
if isContinuousRecognitionActive { callback: ContinuousRecognizeCallback,
audioSourceType: AudioSourceType = .microphone
) -> Bool {
guard speechConfig != nil else {
callback.onError("语音服务未初始化")
return false
}
if _isContinuousRecognitionActive {
return true return true
} }
guard let recognizer = recognizer else { self.audioSourceType = audioSourceType
print("\(tag): 启动连续识别失败: 识别器未初始化")
// 重置识别器
if !setupRecognizer() {
callback.onError("重置识别器失败")
return false return false
} }
do { do {
// 启动连续识别 // 设置各种事件监听
try recognizer.startContinuousRecognition() setupEventListeners(callback: callback)
isContinuousRecognitionActive = true
// 启动音频处理
startAudioProcessing()
// 开始连续识别
try recognizer?.startContinuousRecognition()
_isContinuousRecognitionActive = true
os_log("连续识别已启动,音频源: %{public}@", log: log, type: .info, audioSourceType == .microphone ? "麦克风" : "外部")
return true return true
} catch { } catch {
print("\(tag): 启动连续识别失败: \(error.localizedDescription)") stopAudioProcessing()
_isContinuousRecognitionActive = false
callback.onError("启动连续识别失败: \(error.localizedDescription)")
return false return false
} }
} }
/** /**
* 停止连续识别 * 停止连续语音识别
* *
* @return 是否成功停止 * @return 是否成功停止
*/ */
func stopContinuousRecognition() -> Bool { public func stopContinuousRecognition() -> Bool {
guard let recognizer = recognizer, isContinuousRecognitionActive else { guard speechConfig != nil else {
os_log("语音服务未初始化", log: log, type: .error)
return false
}
if !_isContinuousRecognitionActive {
return true return true
} }
do { do {
guard let recognizer = recognizer else {
os_log("识别器为空,重置状态", log: log, type: .info)
_isContinuousRecognitionActive = false
return true
}
if audioSourceType == .external {
pushAudioData(data: Data())
}
// 停止连续识别 // 停止连续识别
try recognizer.stopContinuousRecognition() try recognizer.stopContinuousRecognition()
isContinuousRecognitionActive = false
// 停止音频处理
stopAudioProcessing()
// 会话结束事件会设置_isContinuousRecognitionActive = false
return true return true
} catch { } catch {
print("\(tag): 停止连续识别失败: \(error.localizedDescription)") // 强制重置状态
return false _isContinuousRecognitionActive = false
} os_log("停止连续识别失败: %{public}@", log: log, type: .error, error.localizedDescription)
}
// 停止音频处理
/** stopAudioProcessing()
* 推送音频数据
* 在连续识别模式下推送新的音频数据 // 尝试强制关闭识别器
* recognizer = nil
* @param audioData 要识别的音频数据字节数组
* @return 是否成功处理
*/
func pushAudioData(audioData: Data) -> Bool {
if !isContinuousRecognitionActive {
return false
}
if audioSourceType != .external {
notifyEvent(eventType: .error, params: ["error": "当前不是外部音频流模式"])
return false return false
} }
// 将音频数据推送到外部音频流
externalAudioStream?.pushAudio(audioData)
return true
} }
/** /**
* 添加ASR事件监听器 * 检查连续识别是否活跃
*
* @param listener 事件监听器
*/ */
func addListener(_ listener: AsrEventListener) { public func isContinuousRecognitionActive() -> Bool {
eventListeners.add(listener as AnyObject) return self._isContinuousRecognitionActive
} }
/** /**
* 移除ASR事件监听器 * 释放所有资源
*
* @param listener 要移除的事件监听器
*/ */
func removeListener(_ listener: AsrEventListener) { public func dispose() {
eventListeners.remove(listener as AnyObject) do {
} // 如果正在进行连续识别,先停止
if _isContinuousRecognitionActive {
/** // 直接停止,不等待结果
* 释放资源 try? recognizer?.stopContinuousRecognition()
* 在不再需要服务时调用,释放底层资源 _isContinuousRecognitionActive = false
*/ }
func dispose() {
// 停止识别 // 停止音频处理
if isContinuousRecognitionActive { stopAudioProcessing()
_ = stopContinuousRecognition()
// 释放资源
recognizer = nil
speechConfig = nil
audioConfig = nil
// 确保状态被重置
_isContinuousRecognitionActive = false
externalAudioStream = nil
} catch {
// 确保状态被重置
_isContinuousRecognitionActive = false
externalAudioStream = nil
audioConfig = nil
recognizer = nil
speechConfig = nil
} }
// 停止音频处理
stopAudioProcessing()
// 释放资源
recognizer = nil
speechConfig = nil
audioConfig = nil
externalAudioStream = nil
}
/**
* 设置识别参数
*
* @param params 参数键值对
* @return 是否设置成功
*/
func setParameters(_ params: [String: Any]) -> Bool {
// 未实现自定义参数,后续可以根据需要添加
return true
} }
/** // MARK: - 私有方法
* 获取连续识别是否活跃
*/
func isRecognitionActive() -> Bool {
return isContinuousRecognitionActive
}
// MARK: - 辅助方法
/** /**
* 创建识别器 * 设置识别器
*
* @return 是否创建成功
*/ */
private func setupRecognizer() -> Bool { private func setupRecognizer() -> Bool {
guard let speechConfig = speechConfig else {
print("\(tag): 无法创建识别器:语音配置不存在")
return false
}
do { do {
if audioSourceType == .external { // 清理旧的识别器
// 设置外部音频流 recognizer = nil
// 设置音频配置
switch audioSourceType {
case .microphone:
// 使用默认麦克风输入配置
audioConfig = try SPXAudioConfiguration()
case .external:
// 使用拉流方式处理外部音频
setupExternalAudioStream() setupExternalAudioStream()
} }
// 根据是否启用自动识别语言创建不同的识别器 // 创建识别器
if isAutoDetectLanguage && supportedLanguages.count > 0 { if isAutoDetectLanguage {
// 创建语言自动检测配置
let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages)
recognizer = try SPXSpeechRecognizer(
if let audioConfig = self.audioConfig { speechConfiguration: speechConfig!,
// 使用指定的音频源创建识别器 autoDetectSourceLanguageConfiguration: autoDetectConfig,
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, audioConfiguration: audioConfig!
autoDetectSourceLanguageConfiguration: autoDetectConfig, )
audioConfiguration: audioConfig)
} else {
// 使用默认音频源创建识别器
// 创建一个默认的麦克风配置而不是传nil
let defaultAudioConfig = SPXAudioConfiguration()
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig,
autoDetectSourceLanguageConfiguration: autoDetectConfig,
audioConfiguration: defaultAudioConfig)
}
} else { } else {
// 使用单一语言配置创建识别器 recognizer = try SPXSpeechRecognizer(
if let audioConfig = self.audioConfig { speechConfiguration: speechConfig!,
// 使用指定的音频源创建识别器 audioConfiguration: audioConfig!
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, )
audioConfiguration: audioConfig)
} else {
// 使用默认音频源创建识别器
// 创建一个默认的麦克风配置
let defaultAudioConfig = SPXAudioConfiguration()
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig,
audioConfiguration: defaultAudioConfig)
}
} }
// 设置事件监听器
setupEventListeners()
return true return true
} catch { } catch {
print("\(tag): 创建识别器失败: \(error.localizedDescription)") os_log("创建识别器失败: %{public}@", log: log, type: .error, error.localizedDescription)
stopAudioProcessing() stopAudioProcessing()
return false return false
} }
} }
/** /**
* 停止音频处理 * 设置外部音频流 - 使用拉流方式
*/
private func stopAudioProcessing() {
// 清理外部音频流
externalAudioStream = nil
}
/**
* 设置外部音频流
*/ */
private func setupExternalAudioStream() { private func setupExternalAudioStream() {
externalAudioStream = ExternalAudioStream() do {
// 使用ExternalAudioStream实例中创建的pullStream // 创建外部音频拉流对象
audioConfig = SPXAudioConfiguration(streamInput: externalAudioStream!.pullStream) externalAudioStream = ExternalAudioPullStream()
// 创建音频配置
audioConfig = SPXAudioConfiguration(streamInput: externalAudioStream!.pullStream)
} catch {
os_log("设置外部音频流失败: %{public}@", log: log, type: .error, error.localizedDescription)
externalAudioStream = nil
}
} }
/** /**
* 设置事件监听器 * 设置事件监听器
*/ */
private func setupEventListeners() { private func setupEventListeners(callback: ContinuousRecognizeCallback) {
guard let recognizer = recognizer else { return } guard let recognizer = recognizer else { return }
// 修改为直接设置事件回调 // 识别中事件
recognizer.addRecognizingEventHandler { [weak self] (sender, event) in recognizer.addRecognizingEventHandler { [weak self] (sender, event) in
guard let self = self else { return } guard let self = self else { return }
var detectedLanguage = ""
// 修改event.result的处理,不使用可选链
let result = event.result let result = event.result
if self.isAutoDetectLanguage { let detectedLanguage = self.getDetectedLanguage(from: result)
// 处理自动语言检测结果 os_log("识别中: %{public}@", log: self.log, type: .info, result.text ?? "")
if let properties = result.properties, // 直接在当前线程调用回调
let language = properties.getPropertyByName("SpeechServiceResponse_RecognitionLanguage") { callback.onRecognizing(result.text ?? "", detectedLanguage)
detectedLanguage = language
}
}
self.notifyEvent(eventType: .recognizing, params: [
"text": result.text,
"language": detectedLanguage
])
} }
// 添加最终识别结果处理器 // 识别完成事件
recognizer.addRecognizedEventHandler { [weak self] (sender, event) in recognizer.addRecognizedEventHandler { [weak self] (sender, event) in
guard let self = self else { return } guard let self = self else { return }
// 修改event.result的处理,不使用可选绑定
let result = event.result let result = event.result
var detectedLanguage = "" if result.reason == SPXResultReason.recognizedSpeech {
let detectedLanguage = self.getDetectedLanguage(from: result)
if self.isAutoDetectLanguage { // 直接在当前线程调用回调
// 处理自动语言检测结果 callback.onResult(result.text ?? "", detectedLanguage)
if let properties = result.properties,
let language = properties.getPropertyByName("SpeechServiceResponse_RecognitionLanguage") {
detectedLanguage = language
}
// 尝试另一种方式获取语言
do {
let langResult = try SPXAutoDetectSourceLanguageResult(result)
if let lang = langResult.language, !lang.isEmpty {
detectedLanguage = lang
}
} catch {
// 忽略错误,继续使用之前获取的语言
}
} }
self.notifyEvent(eventType: .recognized, params: [
"text": result.text,
"language": detectedLanguage
])
} }
// 添加会话开始事件处理器 // 会话开始事件
recognizer.addSessionStartedEventHandler { [weak self] (sender, event) in recognizer.addSessionStartedEventHandler { (sender, event) in
guard let self = self else { return } // 直接在当前线程调用回调
self.notifyEvent(eventType: .sessionStarted) callback.onSessionStarted()
} }
// 添加会话结束事件处理器 // 会话结束事件
recognizer.addSessionStoppedEventHandler { [weak self] (sender, event) in recognizer.addSessionStoppedEventHandler { [weak self] (sender, event) in
guard let self = self else { return } guard let self = self else { return }
self.isContinuousRecognitionActive = false
self.notifyEvent(eventType: .sessionStopped) // 直接在当前线程调用回调
callback.onSessionStopped()
self._isContinuousRecognitionActive = false
self.stopAudioProcessing()
} }
// 添加取消事件处理器 // 取消事件
recognizer.addCanceledEventHandler { [weak self] (sender, event) in recognizer.addCanceledEventHandler { [weak self] (sender, event) in
guard let self = self else { return } guard let self = self else { return }
print("\(self.tag): 识别取消: \(event.errorDetails ?? "未知错误")") let errorDetails = event.errorDetails ?? "未知错误"
let reason = String(describing: event.reason.rawValue)
// 标记连续识别已停止 os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails)
self.isContinuousRecognitionActive = false
// 通知错误 callback.onCanceled(reason, errorDetails)
self.notifyEvent(eventType: .canceled, params: [
"error": "识别取消",
"errorDetails": event.errorDetails ?? "未知错误",
"reason": String(event.reason.rawValue)
])
} }
} }
/** /**
* 触发事件通知 * 获取检测到的语言
*/ */
private func notifyEvent(eventType: AsrEventType, params: [String: Any] = [:]) { private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String {
let event = AsrEvent(type: eventType, params: params) if !isAutoDetectLanguage {
return ""
}
// 在主线程通知事件 // 尝试从属性中获取语言
DispatchQueue.main.async { if let properties = result.properties,
for case let listener as AsrEventListener in self.eventListeners.allObjects { let language = properties.getPropertyByName("SpeechServiceResponse_RecognitionLanguage") {
listener.onEvent(event) return language
} }
// 尝试另一种方式获取语言
do {
let langResult = try SPXAutoDetectSourceLanguageResult(result)
return langResult.language ?? ""
} catch {
return ""
}
}
/**
* 启动音频处理
*/
private func startAudioProcessing() {
switch audioSourceType {
case .microphone:
// 拉流模式不需要额外启动,SDK会自动拉取数据
break
case .external:
// 外部音频数据模式下不需要启动处理,等待外部调用pushAudioData
break
}
}
/**
* 停止音频处理
*/
private func stopAudioProcessing() {
if let stream = externalAudioStream {
stream.close()
externalAudioStream = nil
os_log("外部音频流已关闭", log: log, type: .info)
} }
} }
/** /**
* 外部音频流 - 使用拉流模式 * 外部音频拉流
* 基于NSObject实现,将外部推送的音频数据转换为SDK可拉取的形式 * 实现PullAudioInputStreamCallback,将外部推送的音频数据转换为SDK可拉取的形式
*/ */
class ExternalAudioStream: NSObject { private class ExternalAudioPullStream: NSObject {
// 内部持有一个SPXPullAudioInputStream实例
private(set) var pullStream: SPXPullAudioInputStream! private(set) var pullStream: SPXPullAudioInputStream!
private let queue = LinkedBlockingQueue<Data>()
private var closed = false
// 音频数据缓冲区
private var buffer = Data()
private let lock = NSLock()
// 记录是否已关闭
private var isClosed = false
/**
* 初始化方法
*/
override init() { override init() {
super.init() super.init()
// 创建拉流,使用官方文档推荐的参数类型
pullStream = SPXPullAudioInputStream( pullStream = SPXPullAudioInputStream(
readHandler: { [weak self] (data: NSMutableData, size: UInt) -> Int in readHandler: { [weak self] (data: NSMutableData, size: UInt) -> Int in
guard let self = self else { return 0 } guard let self = self else { return 0 }
self.lock.lock() return self.read(buffer: data, size: Int(size))
defer { self.lock.unlock() }
if self.buffer.isEmpty { return 0 }
let bytesToRead = min(Int(size), self.buffer.count)
data.append(self.buffer.prefix(bytesToRead))
self.buffer.removeFirst(bytesToRead)
return bytesToRead
}, },
closeHandler: { [weak self] in closeHandler: { [weak self] in
self?.close() self?.close()
@ -459,29 +453,166 @@ class AzureAsrHelper: NSObject, IAsrService {
} }
/** /**
* 推送音频数据到缓冲区 * 外部调用:推送音频数据到队列
* * @param data 音频数据
* @param data 要推送的音频数据
*/ */
func pushAudio(_ data: Data) { func pushAudio(_ data: Data) {
if isClosed { return } if !closed {
queue.put(data)
}
}
/**
* SDK调用:从队列中拉取数据
* @param buffer SDK提供的缓冲区
* @param size 缓冲区大小
* @return 读取的字节数,0表示流结束
*/
private func read(buffer: NSMutableData, size: Int) -> Int {
// 阻塞等待下一块数据
guard let chunk = queue.take() else {
return 0 // 队列已关闭
}
// 如果是空数据,表示流结束
if chunk.isEmpty {
return 0
}
let toCopy = min(chunk.count, size)
buffer.append(chunk.prefix(toCopy))
return toCopy
}
/**
* SDK调用:关闭流
*/
func close() {
closed = true
queue.close()
}
}
/**
* iOS版LinkedBlockingQueue实现
* 与Android LinkedBlockingQueue保持一致的API和行为
*/
private class LinkedBlockingQueue<T> {
private var queue: [T] = []
private var isClosed = false
// 使用DispatchSemaphore实现阻塞行为
private let availableItems: DispatchSemaphore
private let queueLock = NSLock()
init() {
self.availableItems = DispatchSemaphore(value: 0)
}
/**
* 阻塞式获取元素(等价于Android的take())
* @return 队列中的元素,如果队列已关闭则返回nil
*/
func take() -> T? {
// 等待可用元素(阻塞直到有元素或队列关闭)
availableItems.wait()
queueLock.lock()
defer { queueLock.unlock() }
// 检查队列是否已关闭
if isClosed && queue.isEmpty {
return nil
}
// 获取第一个元素
guard !queue.isEmpty else {
return nil
}
return queue.removeFirst()
}
/**
* 添加元素(等价于Android的put())
* @param item 要添加的元素
*/
func put(_ item: T) {
queueLock.lock()
defer { queueLock.unlock() }
lock.lock() // 检查队列是否已关闭
defer { lock.unlock() } if isClosed {
return
}
// 添加元素(无容量限制,与Android一致)
queue.append(item)
// 添加到缓冲区 // 通知有新元素可用
buffer.append(data) availableItems.signal()
} }
/** /**
* 关闭流 * 关闭队列
* 关闭后不能再添加新元素,但可以继续取出已有元素
*/ */
func close() { func close() {
lock.lock() queueLock.lock()
defer { lock.unlock() } defer { queueLock.unlock() }
isClosed = true isClosed = true
buffer = Data()
// 唤醒所有等待的take()操作
for _ in 0..<100 { // 假设最多100个等待者
availableItems.signal()
}
} }
} }
/**
* 连续识别回调接口
*/
public protocol ContinuousRecognizeCallback {
/**
* 返回识别结果
*
* @param text 识别的文本
* @param detectedLanguage 检测到的语言
*/
func onResult(_ text: String, _ detectedLanguage: String)
/**
* 识别进行中调用
*
* @param recognizing 正在识别的文本
* @param detectedLanguage 检测到的语言
*/
func onRecognizing(_ recognizing: String, _ detectedLanguage: String)
/**
* 会话开始时调用
*/
func onSessionStarted()
/**
* 会话结束时调用
*/
func onSessionStopped()
/**
* 识别取消时调用
*
* @param reason 取消原因
* @param errorDetails 错误详情
*/
func onCanceled(_ reason: String, _ errorDetails: String)
/**
* 识别出错时调用
*
* @param error 错误信息
*/
func onError(_ error: String)
}
} }

166
local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.swift

@ -4,6 +4,7 @@ import AVFoundation
import MicrosoftCognitiveServicesSpeech import MicrosoftCognitiveServicesSpeech
// 自定义语音处理组件,提供音频流处理等功能 // 自定义语音处理组件,提供音频流处理等功能
import speech import speech
import os.log
/** /**
* Azure Speech Plugin * Azure Speech Plugin
@ -14,6 +15,8 @@ import speech
@objc public class AzureSpeechPlugin: NSObject, FlutterPlugin { @objc public class AzureSpeechPlugin: NSObject, FlutterPlugin {
// 日志标签 // 日志标签
private let tag = "AzureSpeechPlugin" private let tag = "AzureSpeechPlugin"
// 日志对象
private let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin")
// ASR相关 // ASR相关
private var asrChannel: FlutterMethodChannel? private var asrChannel: FlutterMethodChannel?
@ -30,6 +33,9 @@ import speech
// 是否已添加TTS事件监听器 // 是否已添加TTS事件监听器
private var isTtsListenerAdded = false private var isTtsListenerAdded = false
// 当前的连续识别回调
private var currentAsrCallback: AsrCallbackWrapper?
// 插件注册 // 插件注册
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let instance = AzureSpeechPlugin() let instance = AzureSpeechPlugin()
@ -56,9 +62,9 @@ import speech
} }
// 发送ASR事件方法 // 发送ASR事件方法
private func sendAsrEvent(_ event: [String: Any]) { internal func sendAsrEvent(_ event: [String: Any]) {
if asrEventSink == nil { if asrEventSink == nil {
print("\(tag): 无法发送ASR事件:事件通道未准备好") os_log("无法发送ASR事件:事件通道未准备好", log: log, type: .error)
return return
} }
@ -71,7 +77,7 @@ import speech
// 发送TTS事件方法 // 发送TTS事件方法
private func sendTtsEvent(_ event: [String: Any]) { private func sendTtsEvent(_ event: [String: Any]) {
if ttsEventSink == nil { if ttsEventSink == nil {
print("\(tag): 无法发送TTS事件:事件通道未准备好") os_log("无法发送TTS事件:事件通道未准备好", log: log, type: .error)
return return
} }
@ -89,10 +95,7 @@ import speech
} }
} }
// 设置ASR事件监听器
private func setupAsrEventListener() {
azureAsrHelper.addListener(self)
}
// 处理Flutter方法调用 // 处理Flutter方法调用
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@ -115,49 +118,63 @@ import speech
return return
} }
let supportedLanguages = args["supportedLanguages"] as? [String] let supportedLanguages = args["supportedLanguages"] as? [String] ?? ["zh-CN"]
// 初始化ASR引擎并设置事件监听 // 初始化ASR引擎
let success = azureAsrHelper.initialize(subscriptionKey: subscriptionKey, region: region, supportedLanguages: supportedLanguages) let success = azureAsrHelper.initialize(
setupAsrEventListener() subscriptionKey: subscriptionKey,
region: region,
supportedLanguages: supportedLanguages,
audioSourceType: .microphone
)
result(success) result(success)
case "recognizeOnce": case "recognizeOnce":
guard let args = call.arguments as? [String: Any], // iOS版本不支持recognizeOnce
let audioBytes = args["data"] as? FlutterStandardTypedData else { result(FlutterError(code: "NOT_SUPPORTED", message: "iOS版本不支持recognizeOnce", details: nil))
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil))
return
}
// 执行一次性识别
let success = azureAsrHelper.recognizeOnce(audioData: audioBytes.data) { recognizedText in
result(["text": recognizedText, "detectedLanguage": ""])
}
if !success {
result(FlutterError(code: "RECOGNITION_ERROR", message: "启动识别失败", details: nil))
}
case "startContinuousRecognition": case "startContinuousRecognition":
// 确保事件通道已准备好 // 确保事件通道已准备好
if asrEventSink == nil { guard asrEventSink != nil else {
result(FlutterError(code: "EVENT_CHANNEL_NOT_READY", message: "事件通道未准备好,无法开始连续识别", details: nil)) result(FlutterError(code: "EVENT_CHANNEL_NOT_READY", message: "事件通道未准备好,无法开始连续识别", details: nil))
return return
} }
let success = azureAsrHelper.startContinuousRecognition() // 解析音频源类型
let audioSourceType: AzureAsrHelper.AudioSourceType
if let args = call.arguments as? [String: Any],
let audioSourceString = args["audioSourceType"] as? String {
switch audioSourceString.lowercased() {
case "external":
audioSourceType = .external
default:
audioSourceType = .microphone
}
} else {
audioSourceType = .microphone
}
// 创建回调包装器
currentAsrCallback = AsrCallbackWrapper(plugin: self)
let success = azureAsrHelper.startContinuousRecognition(
callback: currentAsrCallback!,
audioSourceType: audioSourceType
)
result(success) result(success)
case "stopContinuousRecognition": case "stopContinuousRecognition":
let success = azureAsrHelper.stopContinuousRecognition() let success = azureAsrHelper.stopContinuousRecognition()
currentAsrCallback = nil
result(success) result(success)
case "isContinuousRecognitionActive": case "isContinuousRecognitionActive":
result(azureAsrHelper.isRecognitionActive()) result(azureAsrHelper.isContinuousRecognitionActive())
case "dispose": case "dispose":
azureAsrHelper.dispose() azureAsrHelper.dispose()
currentAsrCallback = nil
result(true) result(true)
case "pushAudioData": case "pushAudioData":
@ -167,8 +184,8 @@ import speech
return return
} }
let success = azureAsrHelper.pushAudioData(audioData: audioBytes.data) azureAsrHelper.pushAudioData(data: audioBytes.data)
result(success) result(true)
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
@ -247,6 +264,58 @@ import speech
} }
} }
// MARK: - ASR回调包装器
private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
private weak var plugin: AzureSpeechPlugin?
init(plugin: AzureSpeechPlugin) {
self.plugin = plugin
}
func onResult(_ text: String, _ detectedLanguage: String) {
plugin?.sendAsrEvent([
"type": "result",
"text": text,
"language": detectedLanguage
])
}
func onRecognizing(_ recognizing: String, _ detectedLanguage: String) {
plugin?.sendAsrEvent([
"type": "recognizing",
"text": recognizing,
"language": detectedLanguage
])
}
func onSessionStarted() {
plugin?.sendAsrEvent([
"type": "sessionStarted"
])
}
func onSessionStopped() {
plugin?.sendAsrEvent([
"type": "sessionStopped"
])
}
func onCanceled(_ reason: String, _ errorDetails: String) {
plugin?.sendAsrEvent([
"type": "canceled",
"reason": reason,
"errorDetails": errorDetails
])
}
func onError(_ error: String) {
plugin?.sendAsrEvent([
"type": "error",
"error": error
])
}
}
// MARK: - 事件处理 // MARK: - 事件处理
extension AzureSpeechPlugin: FlutterStreamHandler { extension AzureSpeechPlugin: FlutterStreamHandler {
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
@ -296,42 +365,7 @@ extension AzureSpeechPlugin: FlutterStreamHandler {
} }
} }
// MARK: - ASR 事件监听实现
extension AzureSpeechPlugin: AsrEventListener {
// 为ASR事件监听提供明确的选择器名称
@objc(onAsrEvent:)
public func onEvent(_ event: AsrEvent) {
var eventMap: [String: Any] = [
"type": getAsrEventTypeName(event.type)
]
// 添加参数
for (key, value) in event.params {
eventMap[key] = value
}
sendAsrEvent(eventMap)
}
private func getAsrEventTypeName(_ type: AsrEventType) -> String {
switch type {
case .sessionStarted:
return "sessionStarted"
case .sessionStopped:
return "sessionStopped"
case .recognizing:
return "recognizing"
case .recognized:
return "result"
case .canceled:
return "canceled"
case .error:
return "error"
@unknown default:
return "unknown"
}
}
}
// MARK: - TTS 事件监听实现 // MARK: - TTS 事件监听实现
extension AzureSpeechPlugin: TtsEventListener { extension AzureSpeechPlugin: TtsEventListener {

257
local_plugins/azure_speech/ios/Classes/AzureTtsHelper.swift

@ -3,15 +3,27 @@ import AVFoundation
import MicrosoftCognitiveServicesSpeech import MicrosoftCognitiveServicesSpeech
// 自定义语音处理组件,提供音频流处理等功能 // 自定义语音处理组件,提供音频流处理等功能
import speech import speech
import os.log
/** /**
* Azure TTS Helper * Azure TTS Helper
* *
* 基于微软Azure语音服务的TTS实现 * 基于微软Azure语音服务的TTS实现
* 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis * 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis
*
* 特性:
* - 对外接口保持同步,内部异步处理
* - 使用专用队列保证合成顺序
* - 避免阻塞主线程和其他后台线程
* - 事件回调由调用方处理线程切换
*
* 使用示例:
* let success = ttsHelper.speakOnce("Hello World") // 立即返回,后台异步处理
*/ */
class AzureTtsHelper: NSObject, ITtsService { public class AzureTtsHelper: NSObject, ITtsService {
private let tag = "AzureTtsHelper" private let tag = "AzureTtsHelper"
// 日志对象
private let log = OSLog(subsystem: "com.azure.speech", category: "AzureTtsHelper")
private static let DEFAULT_LANGUAGE = "zh-CN" private static let DEFAULT_LANGUAGE = "zh-CN"
private static let DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural" private static let DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural"
@ -36,8 +48,11 @@ class AzureTtsHelper: NSObject, ITtsService {
private var streamBuffer = "" private var streamBuffer = ""
private var lastSpeakTime: TimeInterval = 0 private var lastSpeakTime: TimeInterval = 0
// 音频会话 // 内部异步处理队列,保证顺序执行
private let audioSession = AVAudioSession.sharedInstance() private let synthesisQueue = DispatchQueue(label: "com.azure.tts.synthesis", qos: .userInitiated)
private let synthesisGroup = DispatchGroup()
private var pendingTasks: [() -> Void] = []
private let taskLock = NSLock()
/** /**
* 初始化语音合成服务 * 初始化语音合成服务
@ -48,7 +63,7 @@ class AzureTtsHelper: NSObject, ITtsService {
* @param language 语言代码,如"zh-CN" * @param language 语言代码,如"zh-CN"
* @return 是否初始化成功 * @return 是否初始化成功
*/ */
func initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String, language: String) -> Bool { public func initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String, language: String) -> Bool {
do { do {
// 创建语音配置 // 创建语音配置
if ttsResource.isEmpty { if ttsResource.isEmpty {
@ -83,7 +98,7 @@ class AzureTtsHelper: NSObject, ITtsService {
return true return true
} catch { } catch {
print("\(tag): 语音合成服务初始化失败: \(error.localizedDescription)") os_log("语音合成服务初始化失败: %{public}@", log: log, type: .error, error.localizedDescription)
return false return false
} }
} }
@ -94,7 +109,7 @@ class AzureTtsHelper: NSObject, ITtsService {
* @param voiceName 语音角色名称(不同服务的语音角色命名可能不同) * @param voiceName 语音角色名称(不同服务的语音角色命名可能不同)
* @return 是否设置成功 * @return 是否设置成功
*/ */
func setVoice(_ voiceName: String) -> Bool { public func setVoice(_ voiceName: String) -> Bool {
if !isInitialized { return false } if !isInitialized { return false }
do { do {
@ -109,7 +124,7 @@ class AzureTtsHelper: NSObject, ITtsService {
recreateSynthesizer() recreateSynthesizer()
return true return true
} catch { } catch {
print("\(tag): 设置语音失败: \(error.localizedDescription)") os_log("设置语音失败: %{public}@", log: log, type: .error, error.localizedDescription)
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "VOICE_SET_FAILED", "errorCode": "VOICE_SET_FAILED",
"errorMessage": "设置语音失败: \(error.localizedDescription)" "errorMessage": "设置语音失败: \(error.localizedDescription)"
@ -124,17 +139,71 @@ class AzureTtsHelper: NSObject, ITtsService {
* @param text 要合成的文本 * @param text 要合成的文本
* @return 是否成功开始合成 * @return 是否成功开始合成
*/ */
func speakOnce(_ text: String) -> Bool { public func speakOnce(_ text: String) -> Bool {
if !isInitialized { if !isInitialized {
print("\(tag): 语音合成未初始化") os_log("语音合成未初始化", log: log, type: .error)
return false return false
} }
// 已经在播放,先停止 // 立即返回成功,内部异步处理
if speaking { enqueueSynthesisTask {
_ = stop() self.performSynthesis(text: text)
} }
return true
}
/**
* 将合成任务加入队列,保证顺序执行
*/
private func enqueueSynthesisTask(_ task: @escaping () -> Void) {
taskLock.lock()
defer { taskLock.unlock() }
pendingTasks.append(task)
// 如果当前没有任务在执行,开始处理队列
if pendingTasks.count == 1 {
processNextTask()
}
}
/**
* 处理队列中的下一个任务
*/
private func processNextTask() {
synthesisQueue.async {
self.synthesisGroup.enter()
self.taskLock.lock()
guard !self.pendingTasks.isEmpty else {
self.taskLock.unlock()
self.synthesisGroup.leave()
return
}
let task = self.pendingTasks.removeFirst()
self.taskLock.unlock()
// 执行任务
task()
self.synthesisGroup.leave()
// 处理下一个任务
self.taskLock.lock()
if !self.pendingTasks.isEmpty {
self.taskLock.unlock()
self.processNextTask()
} else {
self.taskLock.unlock()
}
}
}
/**
* 实际执行合成的方法
*/
private func performSynthesis(text: String) {
// 重置状态 // 重置状态
speaking = true speaking = true
@ -142,17 +211,21 @@ class AzureTtsHelper: NSObject, ITtsService {
let ssml = generateSsml(text) let ssml = generateSsml(text)
do { do {
// 创建合成任务 os_log("开始合成: %{public}@", log: log, type: .debug, ssml)
try synthesizer?.startSpeakingSsml(ssml) // 使用同步方法,在后台队列中执行
return true let result = try synthesizer?.startSpeakingSsml(ssml)
// 检查结果
if let result = result {
os_log("合成完成,结果: %{public}@", log: log, type: .debug, String(describing: result.reason))
}
} catch { } catch {
print("\(tag): 语音合成失败: \(error.localizedDescription)") os_log("语音合成失败: %{public}@", log: log, type: .error, error.localizedDescription)
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "SYNTHESIS_FAILED", "errorCode": "SYNTHESIS_FAILED",
"errorMessage": error.localizedDescription "errorMessage": error.localizedDescription
]) ])
speaking = false speaking = false
return false
} }
} }
@ -162,7 +235,7 @@ class AzureTtsHelper: NSObject, ITtsService {
* @param text 要合成的文本片段 * @param text 要合成的文本片段
* @return 是否成功处理 * @return 是否成功处理
*/ */
func speakStream(_ text: String) -> Bool { public func speakStream(_ text: String) -> Bool {
if !isInitialized || text.isEmpty { if !isInitialized || text.isEmpty {
if !isInitialized { if !isInitialized {
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
@ -201,20 +274,22 @@ class AzureTtsHelper: NSObject, ITtsService {
// 如果找到标点符号,则播放到该标点符号 // 如果找到标点符号,则播放到该标点符号
if lastPunctuationIndex >= 0 { if lastPunctuationIndex >= 0 {
// 提取要播放的文本(包含标点符号) // 提取要播放的文本(包含标点符号)
let textToSpeak = String(currentText.prefix(lastPunctuationIndex + 1)) let textToSpeak = String(currentText.prefix(lastPunctuationIndex + 1)).trimmingCharacters(in: .whitespacesAndNewlines)
// 剩余的文本保存在缓冲区中 // 剩余的文本保存在缓冲区中
let startIndex = currentText.index(currentText.startIndex, offsetBy: lastPunctuationIndex + 1) let startIndex = currentText.index(currentText.startIndex, offsetBy: lastPunctuationIndex + 1)
streamBuffer = String(currentText[startIndex...]) streamBuffer = String(currentText[startIndex...])
// 播放提取的文本 // 只有非空文本才播放
if !textToSpeak.isEmpty {
return speakOnce(textToSpeak) return speakOnce(textToSpeak)
}
} }
// 如果没有找到标点符号,则等待更多文本 // 如果没有找到标点符号,则等待更多文本
return true return true
} catch { } catch {
print("\(tag): 流式语音合成失败: \(error.localizedDescription)") os_log("流式语音合成失败: %{public}@", log: log, type: .error, error.localizedDescription)
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "STREAM_FAILED", "errorCode": "STREAM_FAILED",
"errorMessage": "流式语音合成失败: \(error.localizedDescription)" "errorMessage": "流式语音合成失败: \(error.localizedDescription)"
@ -228,7 +303,7 @@ class AzureTtsHelper: NSObject, ITtsService {
* *
* @return 是否成功处理 * @return 是否成功处理
*/ */
func flushStream() -> Bool { public func flushStream() -> Bool {
if !isInitialized { if !isInitialized {
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "NOT_INITIALIZED", "errorCode": "NOT_INITIALIZED",
@ -239,7 +314,7 @@ class AzureTtsHelper: NSObject, ITtsService {
do { do {
// 获取缓冲区中剩余的文本 // 获取缓冲区中剩余的文本
let remainingText = streamBuffer let remainingText = streamBuffer.trimmingCharacters(in: .whitespacesAndNewlines)
// 清空缓冲区 // 清空缓冲区
streamBuffer = "" streamBuffer = ""
@ -252,7 +327,7 @@ class AzureTtsHelper: NSObject, ITtsService {
// 播放剩余文本 // 播放剩余文本
return speakOnce(remainingText) return speakOnce(remainingText)
} catch { } catch {
print("\(tag): 刷新流式文本失败: \(error.localizedDescription)") os_log("刷新流式文本失败: %{public}@", log: log, type: .error, error.localizedDescription)
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "FLUSH_FAILED", "errorCode": "FLUSH_FAILED",
"errorMessage": "刷新流式文本失败: \(error.localizedDescription)" "errorMessage": "刷新流式文本失败: \(error.localizedDescription)"
@ -266,51 +341,70 @@ class AzureTtsHelper: NSObject, ITtsService {
* *
* @return 是否成功停止 * @return 是否成功停止
*/ */
func stop() -> Bool { public func stop() -> Bool {
if !speaking || synthesizer == nil { // 立即更新状态
return true speaking = false
}
do { // 清除流式缓冲区中的待播放内容
// 停止合成 streamBuffer = ""
try synthesizer?.stopSpeaking()
speaking = false // 清空待处理的任务队列
return true taskLock.lock()
} catch { pendingTasks.removeAll()
print("\(tag): 停止语音合成失败: \(error.localizedDescription)") taskLock.unlock()
notifyEvent(eventType: .error, params: [
"errorCode": "STOP_FAILED", // 在后台队列停止合成器,避免阻塞主线程
"errorMessage": error.localizedDescription synthesisQueue.async {
]) if let synthesizer = self.synthesizer {
return false do {
try synthesizer.stopSpeaking()
os_log("语音合成已停止", log: self.log, type: .info)
// 直接通知停止完成
self.notifyEvent(eventType: .synthesisCanceled)
} catch {
os_log("停止语音合成失败: %{public}@", log: self.log, type: .error, error.localizedDescription)
self.notifyEvent(eventType: .error, params: [
"errorCode": "STOP_FAILED",
"errorMessage": error.localizedDescription
])
}
}
} }
return true
} }
/** /**
* 释放资源 * 释放资源
* 在不再需要服务时调用,释放底层资源 * 在不再需要服务时调用,释放底层资源
*/ */
func dispose() { public func dispose() {
do { // 停止播放
// 停止播放 _ = stop()
_ = stop()
// 等待所有任务完成
synthesisGroup.wait()
// 清理资源
synthesisQueue.async {
// 释放合成器 // 释放合成器
synthesizer = nil self.synthesizer = nil
// 释放配置 // 释放配置
speechConfig = nil self.speechConfig = nil
// 清空流缓冲区 // 清空流缓冲区
streamBuffer = "" self.streamBuffer = ""
// 重置状态 // 重置状态
isInitialized = false self.isInitialized = false
speaking = false self.speaking = false
} catch {
// 即使发生异常,也确保重置状态 // 清空任务队列
isInitialized = false self.taskLock.lock()
speaking = false self.pendingTasks.removeAll()
self.taskLock.unlock()
} }
} }
@ -319,7 +413,7 @@ class AzureTtsHelper: NSObject, ITtsService {
* *
* @param listener 事件监听器 * @param listener 事件监听器
*/ */
func addListener(_ listener: TtsEventListener) { public func addListener(_ listener: TtsEventListener) {
eventListeners.add(listener as AnyObject) eventListeners.add(listener as AnyObject)
} }
@ -328,7 +422,7 @@ class AzureTtsHelper: NSObject, ITtsService {
* *
* @param listener 要移除的事件监听器 * @param listener 要移除的事件监听器
*/ */
func removeListener(_ listener: TtsEventListener) { public func removeListener(_ listener: TtsEventListener) {
eventListeners.remove(listener as AnyObject) eventListeners.remove(listener as AnyObject)
} }
@ -338,8 +432,8 @@ class AzureTtsHelper: NSObject, ITtsService {
* *
* @param listener 音频数据监听器 * @param listener 音频数据监听器
*/ */
func addAudioDataListener(_ listener: AudioDataListener) { public func addAudioDataListener(_ listener: AudioDataListener) {
print("\(tag): 警告:不支持音频数据监听器功能") os_log("警告:不支持音频数据监听器功能", log: log, type: .info)
} }
/** /**
@ -348,14 +442,14 @@ class AzureTtsHelper: NSObject, ITtsService {
* *
* @param listener 要移除的音频数据监听器 * @param listener 要移除的音频数据监听器
*/ */
func removeAudioDataListener(_ listener: AudioDataListener) { public func removeAudioDataListener(_ listener: AudioDataListener) {
// 不做任何操作 // 不做任何操作
} }
/** /**
* 当前是否正在播放/合成 * 当前是否正在播放/合成
*/ */
func isSpeaking() -> Bool { public func isSpeaking() -> Bool {
return speaking return speaking
} }
@ -389,7 +483,6 @@ class AzureTtsHelper: NSObject, ITtsService {
synthesizer.addSynthesisCanceledEventHandler { [weak self] _, e in synthesizer.addSynthesisCanceledEventHandler { [weak self] _, e in
guard let self = self else { return } guard let self = self else { return }
print("\(self.tag): 语音合成取消")
self.speaking = false self.speaking = false
var params: [String: Any] = [:] var params: [String: Any] = [:]
@ -402,7 +495,8 @@ class AzureTtsHelper: NSObject, ITtsService {
} catch { } catch {
params["errorDetails"] = "获取取消详情失败: \(error.localizedDescription)" params["errorDetails"] = "获取取消详情失败: \(error.localizedDescription)"
} }
os_log("语音合成取消, %{public}@", log: self.log, type: .info, String(describing: params["errorDetails"] ?? "未知错误"))
self.notifyEvent(eventType: .synthesisCanceled, params: params) self.notifyEvent(eventType: .synthesisCanceled, params: params)
} }
} }
@ -413,11 +507,9 @@ class AzureTtsHelper: NSObject, ITtsService {
private func notifyEvent(eventType: TtsEventType, params: [String: Any] = [:]) { private func notifyEvent(eventType: TtsEventType, params: [String: Any] = [:]) {
let event = TtsEvent(type: eventType, params: params) let event = TtsEvent(type: eventType, params: params)
// 在主线程通知事件 // 直接通知事件,由调用方处理线程切换
DispatchQueue.main.async { for case let listener as TtsEventListener in self.eventListeners.allObjects {
for case let listener as TtsEventListener in self.eventListeners.allObjects { listener.onEvent(event)
listener.onEvent(event)
}
} }
} }
@ -432,7 +524,7 @@ class AzureTtsHelper: NSObject, ITtsService {
// 设置事件监听 // 设置事件监听
setupEventListeners() setupEventListeners()
} catch { } catch {
print("\(tag): 重新创建合成器失败: \(error.localizedDescription)") os_log("重新创建合成器失败: %{public}@", log: log, type: .error, error.localizedDescription)
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "RECREATE_FAILED", "errorCode": "RECREATE_FAILED",
"errorMessage": "重新创建合成器失败: \(error.localizedDescription)" "errorMessage": "重新创建合成器失败: \(error.localizedDescription)"
@ -452,7 +544,7 @@ class AzureTtsHelper: NSObject, ITtsService {
currentVolume = "\(min(max(volume, 0), 100))%" currentVolume = "\(min(max(volume, 0), 100))%"
return true return true
} catch { } catch {
print("\(tag): 设置语音参数失败: \(error.localizedDescription)") os_log("设置语音参数失败: %{public}@", log: log, type: .error, error.localizedDescription)
notifyEvent(eventType: .error, params: [ notifyEvent(eventType: .error, params: [
"errorCode": "PARAMS_SET_FAILED", "errorCode": "PARAMS_SET_FAILED",
"errorMessage": "设置语音参数失败: \(error.localizedDescription)" "errorMessage": "设置语音参数失败: \(error.localizedDescription)"
@ -472,22 +564,37 @@ class AzureTtsHelper: NSObject, ITtsService {
* 生成SSML * 生成SSML
*/ */
private func generateSsml(_ rawText: String) -> String { private func generateSsml(_ rawText: String) -> String {
// 转义 XML 保留字符 // 1. 定义要静音的符号和表情符号列表
let symbolsToMute = [
"#", "*",
"😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉"
]
// 2. 转义 XML 保留字符
var escapedText = rawText var escapedText = rawText
.replacingOccurrences(of: "&", with: "&amp;") .replacingOccurrences(of: "&", with: "&amp;")
.replacingOccurrences(of: "<", with: "&lt;") .replacingOccurrences(of: "<", with: "&lt;")
.replacingOccurrences(of: ">", with: "&gt;") .replacingOccurrences(of: ">", with: "&gt;")
// 构造简化的SSML文档 // 3. 静音处理特殊符号和表情符号
// 使用空白替换法,直接将符号替换为空字符串
var processedText = escapedText
for symbol in symbolsToMute {
processedText = processedText.replacingOccurrences(of: symbol, with: "")
}
// 4. 构造简化的SSML文档,减少嵌套层级
let ssml = """ let ssml = """
<speak version="1.0" <speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis" xmlns="http://www.w3.org/2001/10/synthesis"
xmlns:mstts="https://www.w3.org/2001/mstts" xmlns:mstts="https://www.w3.org/2001/mstts"
xml:lang="zh-CN"> xml:lang="zh-CN">
<voice name="\(currentVoice)"> <voice name="\(currentVoice)">
<prosody rate="\(currentRate)" pitch="\(currentPitch)" volume="\(currentVolume)"> <mstts:express-as style="cheerful">
\(escapedText) <prosody rate="\(currentRate)" pitch="\(currentPitch)" volume="\(currentVolume)">
</prosody> <say-as interpret-as="text">\(processedText)</say-as>
</prosody>
</mstts:express-as>
</voice> </voice>
</speak> </speak>
""" """

2
local_plugins/azure_speech/ios/azure_speech.podspec

@ -16,7 +16,7 @@ A Flutter plugin for Azure Speech Services providing speech recognition and text
s.source_files = 'Classes/**/*.{swift}' s.source_files = 'Classes/**/*.{swift}'
s.dependency 'Flutter' s.dependency 'Flutter'
s.dependency 'speech' s.dependency 'speech'
s.dependency 'MicrosoftCognitiveServicesSpeech-iOS', '~> 1.30.0' s.dependency 'MicrosoftCognitiveServicesSpeech-iOS', '~> 1.37.0'
s.platform = :ios, '11.0' s.platform = :ios, '11.0'
# Flutter.framework does not contain a i386 slice. # Flutter.framework does not contain a i386 slice.

406
local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift

@ -6,317 +6,222 @@ import AccessorySetupKit
import UIKit import UIKit
class BleAccessorySetup: NSObject { class BleAccessorySetup: NSObject {
// 单例实现 // MARK: - 属性
static let shared = BleAccessorySetup() static let shared = BleAccessorySetup()
// 基本属性
private var bleService: BleService? private var bleService: BleService?
private var session: ASAccessorySession? private var session: ASAccessorySession?
private var isSessionActivated = false private var isSessionActivated = false
private var isInitializing = false private var isInitializing = false
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup")
private var onSessionActivated: (() -> Void)? // 杰理蓝牙设备的公司标识符
private let deviceCompanyIdentifier: UInt16 = 0xEFAB
private let deviceDisplayName = "语音助理耳机"
typealias DeviceDiscoveredCallback = (UUID) -> Void typealias DeviceDiscoveredCallback = (UUID) -> Void
private var onDeviceDiscovered: DeviceDiscoveredCallback? private var onDeviceDiscovered: DeviceDiscoveredCallback?
private override init() { // MARK: - 初始化
super.init() private override init() { super.init() }
}
// 设置BLE服务实例 // MARK: - 公共方法
func setBleService(_ service: Any) { func setBleService(_ service: Any) {
self.bleService = service as? BleService self.bleService = service as? BleService
os_log("已设置BLE服务实例", log: logger, type: .debug)
} }
// 设置设备发现回调
func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) {
self.onDeviceDiscovered = callback self.onDeviceDiscovered = callback
} }
// 初始化设备发现会话 // 处理发现的设备并连接
func initializeDiscoverySession() { func connectToAccessory(uuid: UUID) {
os_log("开始初始化设备发现会话", log: logger, type: .info) guard let service = bleService else {
os_log("BleService未设置,无法连接到设备", log: logger, type: .error)
return
}
if isInitializing || isSessionActivated { // 连接设备
os_log("会话已在初始化或已激活,跳过初始化", log: logger, type: .debug) service.connectToAccessory(uuid: uuid)
}
// 从视图控制器显示设备选择器 - 主要入口点
func showDevicePickerFromViewController(_ viewController: UIViewController) {
// 初始化会话(如果尚未初始化)
initializeSession()
}
// 清除蓝牙状态(仅用于测试或重置,不会清除系统授权)
func resetBluetoothState() {
if let service = bleService {
service.disconnect()
}
}
// MARK: - 会话管理
func initializeSession() {
// 检查是否已在初始化或已初始化
if isInitializing || (session != nil && isSessionActivated) {
os_log("会话已在初始化或已激活", log: logger, type: .debug)
return return
} }
// 开始初始化过程
isInitializing = true isInitializing = true
os_log("初始化设备发现会话", log: logger, type: .info)
do { do {
os_log("创建ASAccessorySession", log: logger, type: .debug)
session = try ASAccessorySession() session = try ASAccessorySession()
os_log("激活会话", log: logger, type: .debug)
session?.activate(on: .main) { [weak self] event in session?.activate(on: .main) { [weak self] event in
guard let self = self else { return } guard let self = self else { return }
self.handleSessionEvent(event)
os_log("收到会话事件: %{public}@", log: self.logger, type: .debug, String(describing: event.eventType))
switch event.eventType {
case .activated:
os_log("会话已激活", log: self.logger, type: .info)
self.isSessionActivated = true
DispatchQueue.main.async(execute: DispatchWorkItem(block: {
self.onSessionActivated?()
self.onSessionActivated = nil
}))
case .accessoryAdded:
if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier {
os_log("发现设备: %{public}@", log: self.logger, type: .info, uuid.uuidString)
// 记录设备详细信息
let name = "设备_\(uuid.uuidString.prefix(8))"
os_log("设备名称: %{public}@", log: self.logger, type: .debug, name)
// 直接获取公司ID无需使用可选链
let descriptor = accessory.descriptor
let companyIdentifier = descriptor.bluetoothCompanyIdentifier.rawValue
os_log("设备公司ID: 0x%{public}x", log: self.logger, type: .debug, companyIdentifier)
self.onDeviceDiscovered?(uuid)
} else {
os_log("发现设备但缺少UUID信息", log: self.logger, type: .error)
if let accessory = event.accessory {
os_log("设备信息: %{public}@", log: self.logger, type: .debug, String(describing: accessory))
}
}
case .accessoryRemoved:
os_log("设备已移除", log: self.logger, type: .debug)
if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier {
os_log("移除设备UUID: %{public}@", log: self.logger, type: .debug, uuid.uuidString)
}
case .accessoryChanged:
os_log("设备已变更", log: self.logger, type: .debug)
case .pickerSetupFailed:
os_log("选择器设置失败: %{public}@", log: self.logger, type: .error, event.error?.localizedDescription ?? "未知错误")
default:
os_log("未处理的事件类型: %{public}@", log: self.logger, type: .debug, String(describing: event.eventType))
break
}
}
// 处理现有授权设备
if let accessories = session?.accessories {
os_log("检查当前已授权设备:共%d个", log: logger, type: .debug, accessories.count)
for accessory in accessories {
if let uuid = accessory.bluetoothIdentifier {
os_log("处理已授权设备:%@", log: logger, type: .info, uuid.uuidString)
let name = "设备_\(uuid.uuidString.prefix(8))"
os_log("已授权设备名称:%@", log: logger, type: .debug, name)
self.onDeviceDiscovered?(uuid)
} else {
os_log("发现已授权设备但无UUID", log: logger, type: .error)
}
}
} else {
os_log("无已授权设备", log: logger, type: .debug)
} }
} catch { } catch {
// 出错时重置状态 handleSessionInitError(error)
os_log("初始化会话失败:%@", log: logger, type: .error, error.localizedDescription)
isInitializing = false
session = nil
DispatchQueue.main.async(execute: DispatchWorkItem(block: {
self.onSessionActivated?()
self.onSessionActivated = nil
}))
} }
isInitializing = false
os_log("设备发现会话初始化完成", log: logger, type: .debug)
} }
// 初始化会话并在完成后执行回调
func initializeSessionAndRun(completion: @escaping () -> Void) {
// 如果会话已存在且已激活
if session != nil && isSessionActivated {
completion()
return
}
// 如果会话存在但未激活,设置回调
if session != nil && !isSessionActivated {
self.onSessionActivated = completion
return
}
// 需要从头创建会话
self.onSessionActivated = completion
initializeDiscoverySession()
}
// 重置会话状态
func resetSession() { func resetSession() {
// 取消可能挂起的回调
onSessionActivated = nil
// 清理现有会话
session?.invalidate() session?.invalidate()
// 重置状态
session = nil session = nil
isSessionActivated = false isSessionActivated = false
isInitializing = false isInitializing = false
os_log("会话已重置", log: logger, type: .debug)
} }
// 安全显示设备选择器 // MARK: - 设备选择器显示
func safeShowDevicePicker() { private func showDevicePicker() {
resetSession() guard let session = session, isSessionActivated else {
os_log("会话未激活,无法显示设备选择器", log: logger, type: .error)
initializeSessionAndRun { [weak self] in
guard let self = self else { return }
self.showDevicePicker()
}
}
// 显示设备选择器
func showDevicePicker() {
os_log("尝试显示设备选择器", log: logger, type: .info)
// 检查会话是否已初始化
if session == nil {
os_log("会话未初始化,重新安全初始化", log: logger, type: .debug)
safeShowDevicePicker()
return return
} }
// 检查会话是否已激活 // 创建设备描述符
if !isSessionActivated {
os_log("会话尚未激活,等待激活后再显示选择器", log: logger, type: .debug)
onSessionActivated = { [weak self] in
self?.showDevicePicker()
}
return
}
// 创建设备描述符和显示项
os_log("创建设备描述符", log: logger, type: .debug)
let descriptor = ASDiscoveryDescriptor() let descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier)
os_log("设置公司标识符:0x%x", log: logger, type: .debug, 0xEFAB)
// 检查和记录发现设置
if let session = self.session {
os_log("当前会话设置已激活: %{public}@", log: logger, type: .debug,
String(describing: self.isSessionActivated))
let currentAccessories = session.accessories
os_log("当前会话有%d个已知设备", log: logger, type: .debug, currentAccessories.count)
}
// 创建默认图像
let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
// 创建显示项 // 创建显示项
let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
let displayItem = ASPickerDisplayItem( let displayItem = ASPickerDisplayItem(
name: "语音助理耳机", name: deviceDisplayName,
productImage: deviceImage, productImage: deviceImage,
descriptor: descriptor descriptor: descriptor
) )
os_log("创建显示项:语音助理耳机", log: logger, type: .debug)
// 显示选择器 os_log("显示设备选择器", log: logger, type: .info)
os_log("即将显示设备选择器", log: logger, type: .debug) DispatchQueue.main.async {
DispatchQueue.main.async(execute: DispatchWorkItem(block: { session.showPicker(for: [displayItem]) { [weak self] error in
self.session?.showPicker(for: [displayItem]) { error in guard let self = self else { return }
if let error = error { if let error = error {
os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, let nsError = error as NSError
error.localizedDescription) os_log("设备选择器错误: %{public}@, 域: %{public}@, 错误码: %d",
log: self.logger, type: .error,
error.localizedDescription, nsError.domain, nsError.code)
} else { } else {
os_log("设备选择器已显示", log: self.logger, type: .debug) os_log("设备选择器已显示", log: self.logger, type: .debug)
} }
} }
}))
}
// 处理发现的设备
func handleDiscoveredAccessory(uuid: UUID) {
guard let service = bleService else {
return
} }
service.connectToAccessory(uuid: uuid)
} }
// 从指定的视图控制器显示设备选择器 // MARK: - 私有辅助方法
func showDevicePickerFromViewController(_ viewController: UIViewController) { private func handleSessionEvent(_ event: ASAccessoryEvent) {
safeShowDevicePickerWithUI(presentingVC: viewController) switch event.eventType {
case .activated:
handleSessionActivated()
case .accessoryAdded:
handleAccessoryAdded(event.accessory)
case .accessoryRemoved:
handleAccessoryRemoved(event.accessory)
case .accessoryChanged:
os_log("设备已变更", log: logger, type: .debug)
case .pickerDidPresent:
os_log("选择器已显示", log: logger, type: .info)
case .pickerDidDismiss:
os_log("选择器已关闭", log: logger, type: .info)
case .pickerSetupFailed:
os_log("选择器设置失败: %{public}@", log: logger, type: .error,
event.error?.localizedDescription ?? "未知错误")
default:
os_log("未处理的事件类型: %d", log: logger, type: .debug, event.eventType.rawValue)
}
} }
// 带UI上下文的安全显示选择器 private func handleSessionActivated() {
private func safeShowDevicePickerWithUI(presentingVC: UIViewController) { os_log("会话已激活", log: logger, type: .info)
os_log("准备从视图控制器显示设备选择器", log: logger, type: .info) isSessionActivated = true
resetSession() isInitializing = false
initializeSessionAndRun { [weak self, weak presentingVC] in // 检查是否有已授权设备
guard let self = self, let presentingVC = presentingVC else { if let accessories = session?.accessories, !accessories.isEmpty {
os_log("视图控制器或self已被释放", log: self?.logger ?? OSLog.default, type: .error) // 二次进入:已有授权设备,可以先创建Central再连接
return os_log("发现%d个已授权设备", log: logger, type: .info, accessories.count)
}
if self.session != nil && self.isSessionActivated { // 初始化BleService的Central
os_log("会话已激活,准备显示设备选择器UI", log: self.logger, type: .debug) if let service = bleService {
DispatchQueue.main.async { // 确保CBCentralManager已初始化
let descriptor = ASDiscoveryDescriptor() let _ = service.getCentralManager()
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB)
os_log("设置公司标识符:0x%x", log: self.logger, type: .debug, 0xEFAB) // 连接所有授权设备
for accessory in accessories {
os_log("描述符配置: %{public}@", log: self.logger, type: .debug, if let uuid = accessory.bluetoothIdentifier {
String(describing: descriptor)) os_log("连接已授权设备: %{public}@", log: logger, type: .info, uuid.uuidString)
onDeviceDiscovered?(uuid)
let deviceImage = UIImage(systemName: "headphones") ?? UIImage() connectToAccessory(uuid: uuid)
let displayItem = ASPickerDisplayItem(
name: "语音助理耳机",
productImage: deviceImage,
descriptor: descriptor
)
os_log("显示设备选择器UI", log: self.logger, type: .debug)
self.session?.showPicker(for: [displayItem]) { error in
os_log("设备选择器UI回调", log: self.logger, type: .debug)
if let error = error {
os_log("设备选择器返回错误: %{public}@", log: self.logger, type: .error,
error.localizedDescription)
let nsError = error as NSError
os_log("错误域: %{public}@, 错误码: %d", log: self.logger, type: .error,
nsError.domain, nsError.code)
}
} }
} }
} else {
os_log("会话未激活或为nil,无法显示设备选择器", log: self.logger, type: .error)
} }
} else {
// 首次配对:不创建Central,直接显示设备选择器
os_log("没有已授权设备,显示设备选择器", log: logger, type: .info)
showDevicePicker()
} }
} }
// 检查蓝牙是否可用 private func handleAccessoryAdded(_ accessory: ASAccessory?) {
func isBluetoothAvailable() -> Bool { os_log("发现已授权设备: %{public}@", log: logger, type: .info, accessory?.bluetoothIdentifier?.uuidString ?? "Unknown")
// 日志输出
os_log("检查蓝牙可用性", log: logger, type: .debug) guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
os_log("发现设备但缺少UUID信息", log: logger, type: .error)
// 默认返回true,适用于iOS 18环境 return
if bleService == nil {
os_log("BleService未设置,默认返回true", log: logger, type: .info)
return true
} }
// 尝试类型转换
guard let _ = bleService as? BleService else { // 通知发现设备并连接
os_log("BleService类型转换失败", log: logger, type: .debug) onDeviceDiscovered?(uuid)
return true connectToAccessory(uuid: uuid)
}
private func handleAccessoryRemoved(_ accessory: ASAccessory?) {
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
return
} }
os_log("设备已移除授权: %{public}@", log: logger, type: .info, uuid.uuidString)
// 断开连接
if let service = bleService {
service.disconnect()
}
}
private func handleSessionInitError(_ error: Error) {
os_log("初始化会话失败: %{public}@", log: logger, type: .error, error.localizedDescription)
isInitializing = false
session = nil
}
// MARK: - 蓝牙状态检查
func isBluetoothAvailable() -> Bool {
// 使用AVAudioSession检查蓝牙连接 // 使用AVAudioSession检查蓝牙连接
do { do {
let audioSession = AVAudioSession.sharedInstance() let audioSession = AVAudioSession.sharedInstance()
@ -327,18 +232,37 @@ class BleAccessorySetup: NSObject {
if output.portType == .bluetoothA2DP || if output.portType == .bluetoothA2DP ||
output.portType == .bluetoothHFP || output.portType == .bluetoothHFP ||
output.portType == .bluetoothLE { output.portType == .bluetoothLE {
os_log("发现蓝牙音频输出设备", log: logger, type: .debug)
return true return true
} }
} }
// 未找到蓝牙设备 os_log("未检测到蓝牙音频设备", log: logger, type: .debug)
os_log("未找到蓝牙音频设备", log: logger, type: .debug)
} catch { } catch {
os_log("检查音频会话出错: %{public}@", log: logger, type: .error, error.localizedDescription) os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription)
} }
// 默认返回true,避免阻止功能 // 默认返回true避免阻止功能
return true return true
} }
// MARK: - 清除配对关系
func clearAssociations() {
os_log("清除设备配对关系", log: logger, type: .info)
// 先断开当前连接
resetBluetoothState()
// 清除会话授权关系
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
// 方法1: 重置整个会话
resetSession()
// 重新初始化会话
initializeSession()
os_log("会话已重置,所有授权关系已清除", log: logger, type: .info)
}
#endif
}
} }

464
local_plugins/ble_service/ios/Classes/BleService.swift

@ -1,16 +1,15 @@
import Foundation import Foundation
import CoreBluetooth import CoreBluetooth
import os.log import os.log
import SpeexKit // 使用杰理的SpeexKit库替代OpusSwift
import AVFoundation import AVFoundation
@available(iOS 13.0, *) @available(iOS 13.0, *)
class BleService: NSObject { public class BleService: NSObject {
// MARK: - 单例实现 // MARK: - 单例实现
static let shared = BleService() public static let shared = BleService()
// MARK: - 回调代理协议 // MARK: - 回调代理协议
protocol Callback: AnyObject { public protocol Callback: AnyObject {
// 扫描相关回调 // 扫描相关回调
func onScanResult(devices: [[String: Any]]) func onScanResult(devices: [[String: Any]])
@ -38,10 +37,20 @@ class BleService: NSObject {
private var callbacks = [Callback]() private var callbacks = [Callback]()
private var connectionState = BleConst.STATE_DISCONNECTED private var connectionState = BleConst.STATE_DISCONNECTED
// 待连接设备UUID队列
private var pendingUUIDs = [UUID]()
// 连接超时检测
private var connectionTimeoutTimer: Timer?
private let connectionTimeoutSeconds: TimeInterval = 15 // 15秒连接超时
// 需要重置的设备(从willRestoreState记录,在poweredOn时重置)
private var devicesToReset: [CBPeripheral] = []
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService") private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService")
// MARK: - Opus音频处理器 // MARK: - Opus音频处理器
private var opusProcessor: OpusAudioProcessor? private var opusProcessor: SwiftOpusAudioProcessor?
// MARK: - 初始化 // MARK: - 初始化
private override init() { private override init() {
@ -64,20 +73,23 @@ class BleService: NSObject {
// 延迟初始化中央管理器 // 延迟初始化中央管理器
private func initializeCentralManager() { private func initializeCentralManager() {
if centralManager != nil { if centralManager != nil {
os_log("🔧 CBCentralManager已存在,跳过初始化", log: logger, type: .debug)
return return
} }
os_log("🔧 开始初始化CBCentralManager", log: logger, type: .info)
let options: [String: Any] = [ let options: [String: Any] = [
CBCentralManagerOptionRestoreIdentifierKey: "BLEAssistantCentral", CBCentralManagerOptionRestoreIdentifierKey: "BLEAssistantCentral",
CBCentralManagerOptionShowPowerAlertKey: true CBCentralManagerOptionShowPowerAlertKey: true
] ]
centralManager = CBCentralManager(delegate: self, queue: nil, options: options) centralManager = CBCentralManager(delegate: self, queue: nil, options: options)
os_log("🔧 CBCentralManager初始化完成,等待状态更新", log: logger, type: .debug)
} }
// 初始化Opus处理器 // 初始化Opus处理器
private func initializeOpusProcessor() { private func initializeOpusProcessor() {
opusProcessor = OpusAudioProcessor() opusProcessor = SwiftOpusAudioProcessor()
opusProcessor?.setCallback(self) opusProcessor?.setCallback(self)
// 立即启动Opus解码流,保持整个生命周期内活跃 // 立即启动Opus解码流,保持整个生命周期内活跃
@ -102,7 +114,7 @@ class BleService: NSObject {
// MARK: - 回调管理 // MARK: - 回调管理
/// 添加回调代理 /// 添加回调代理
func addCallback(_ callback: Callback) -> Bool { public func addCallback(_ callback: Callback) -> Bool {
if !callbacks.contains(where: { $0 === callback }) { if !callbacks.contains(where: { $0 === callback }) {
callbacks.append(callback) callbacks.append(callback)
os_log("已添加BLE回调, 当前回调数: %d", log: logger, type: .debug, callbacks.count) os_log("已添加BLE回调, 当前回调数: %d", log: logger, type: .debug, callbacks.count)
@ -112,7 +124,7 @@ class BleService: NSObject {
} }
/// 移除回调代理 /// 移除回调代理
func removeCallback(_ callback: Callback) -> Bool { public func removeCallback(_ callback: Callback) -> Bool {
if let index = callbacks.firstIndex(where: { $0 === callback }) { if let index = callbacks.firstIndex(where: { $0 === callback }) {
callbacks.remove(at: index) callbacks.remove(at: index)
os_log("已移除BLE回调, 当前回调数: %d", log: logger, type: .debug, callbacks.count) os_log("已移除BLE回调, 当前回调数: %d", log: logger, type: .debug, callbacks.count)
@ -122,15 +134,26 @@ class BleService: NSObject {
} }
/// 清除所有回调 /// 清除所有回调
func clearCallbacks() { public func clearCallbacks() {
callbacks.removeAll() callbacks.removeAll()
os_log("已清除所有BLE回调", log: logger, type: .debug) os_log("已清除所有BLE回调", log: logger, type: .debug)
} }
/// 设置代理(单一代理模式,用于替代addCallback)
public func setDelegate(_ delegate: Callback?) {
callbacks.removeAll()
if let delegate = delegate {
callbacks.append(delegate)
os_log("设置BLE单一代理", log: logger, type: .debug)
} else {
os_log("清除BLE单一代理", log: logger, type: .debug)
}
}
// MARK: - 公共方法 // MARK: - 公共方法
/// 获取中央管理器,如果未初始化则先初始化 /// 获取中央管理器,如果未初始化则先初始化
func getCentralManager() -> CBCentralManager { public func getCentralManager() -> CBCentralManager {
if centralManager == nil { if centralManager == nil {
initializeCentralManager() initializeCentralManager()
} }
@ -138,7 +161,7 @@ class BleService: NSObject {
} }
/// 开始扫描BLE设备 /// 开始扫描BLE设备
func startScan() { public func startScan() {
#if canImport(AccessorySetupKit) && swift(>=5.9) #if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
// iOS 18+不使用传统扫描 // iOS 18+不使用传统扫描
@ -169,7 +192,7 @@ class BleService: NSObject {
} }
/// 停止扫描 /// 停止扫描
func stopScan() { public func stopScan() {
if isScanning && centralManager != nil { if isScanning && centralManager != nil {
centralManager?.stopScan() centralManager?.stopScan()
isScanning = false isScanning = false
@ -178,7 +201,7 @@ class BleService: NSObject {
} }
/// 连接到指定设备 /// 连接到指定设备
func connect(to peripheral: CBPeripheral) { public func connect(to peripheral: CBPeripheral) {
if connectionState == BleConst.STATE_CONNECTED || connectionState == BleConst.STATE_CONNECTING { if connectionState == BleConst.STATE_CONNECTED || connectionState == BleConst.STATE_CONNECTING {
return return
} }
@ -201,11 +224,14 @@ class BleService: NSObject {
CBConnectPeripheralOptionNotifyOnNotificationKey: true CBConnectPeripheralOptionNotifyOnNotificationKey: true
]) ])
// 启动连接超时检测
startConnectionTimeout()
os_log("开始连接到设备: %@", log: logger, type: .info, peripheral.identifier.uuidString) os_log("开始连接到设备: %@", log: logger, type: .info, peripheral.identifier.uuidString)
} }
/// 断开当前连接 /// 断开当前连接
func disconnect() { public func disconnect() {
guard let peripheral = peripheralDevice, guard let peripheral = peripheralDevice,
connectionState == BleConst.STATE_CONNECTED else { connectionState == BleConst.STATE_CONNECTED else {
return return
@ -219,7 +245,7 @@ class BleService: NSObject {
} }
/// 检查是否已连接 /// 检查是否已连接
func isConnected() -> Bool { public func isConnected() -> Bool {
return connectionState == BleConst.STATE_CONNECTED return connectionState == BleConst.STATE_CONNECTED
} }
@ -294,9 +320,22 @@ class BleService: NSObject {
/// 打开编码器 /// 打开编码器
func openEncoder() -> Bool { func openEncoder() -> Bool {
os_log("打开编码(设备侧)...", log: logger, type: .info) os_log("🎙️ 准备打开编码(设备侧)...", log: logger, type: .info)
os_log("🔍 状态检查: 连接状态=%d (期望=%d), writeCharacteristic=%@",
log: logger, type: .debug,
connectionState, BleConst.STATE_CONNECTED,
writeCharacteristic != nil ? "已设置" : "未设置")
let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT])
return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) let result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData)
if result {
os_log("✅ 编码器命令发送成功", log: logger, type: .info)
} else {
os_log("❌ 编码器命令发送失败", log: logger, type: .error)
}
return result
} }
/// 打开解码器 /// 打开解码器
@ -367,11 +406,56 @@ class BleService: NSObject {
return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData)
} }
// MARK: - 连接超时检测
/// 启动连接超时检测
private func startConnectionTimeout() {
stopConnectionTimeout() // 先停止现有的检测
connectionTimeoutTimer = Timer.scheduledTimer(withTimeInterval: connectionTimeoutSeconds, repeats: false) { [weak self] _ in
guard let self = self else { return }
os_log("⏰ 连接超时,取消连接", log: self.logger, type: .debug)
// 取消连接
if let peripheral = self.peripheralDevice {
self.centralManager?.cancelPeripheralConnection(peripheral)
}
self.updateConnectionState(BleConst.STATE_ERROR)
}
os_log("⏰ 连接超时检测已启动,%d秒后超时", log: logger, type: .debug, Int(connectionTimeoutSeconds))
}
/// 停止连接超时检测
private func stopConnectionTimeout() {
connectionTimeoutTimer?.invalidate()
connectionTimeoutTimer = nil
}
// MARK: - 私有方法 // MARK: - 私有方法
/// 更新连接状态并通知回调 /// 更新连接状态并通知回调
private func updateConnectionState(_ state: Int) { private func updateConnectionState(_ state: Int) {
let oldState = connectionState
connectionState = state connectionState = state
let stateDesc = { (state: Int) -> String in
switch state {
case BleConst.STATE_DISCONNECTED: return "DISCONNECTED"
case BleConst.STATE_CONNECTING: return "CONNECTING"
case BleConst.STATE_CONNECTED: return "CONNECTED"
case BleConst.STATE_DISCONNECTING: return "DISCONNECTING"
case BleConst.STATE_ERROR: return "ERROR"
default: return "UNKNOWN(\(state))"
}
}
if oldState != state {
os_log("📱 BLE连接状态变化: %@ → %@", log: logger, type: .info, stateDesc(oldState), stateDesc(state))
}
notifyConnectionStateChanged(state: state) notifyConnectionStateChanged(state: state)
} }
@ -410,8 +494,8 @@ class BleService: NSObject {
os_log("发送命令: CMD=0x%02X, 长度=%d, 数据=[%@], CRC=0x%02X", os_log("发送命令: CMD=0x%02X, 长度=%d, 数据=[%@], CRC=0x%02X",
log: logger, type: .debug, cmd, cmdLength, dataHexString, crc) log: logger, type: .debug, cmd, cmdLength, dataHexString, crc)
// 发送数据 // 发送数据(使用无响应写入,与Android端保持一致)
peripheralDevice?.writeValue(cmdPacket, for: characteristic, type: .withResponse) peripheralDevice?.writeValue(cmdPacket, for: characteristic, type: .withoutResponse)
return true return true
} catch { } catch {
os_log("发送命令异常: %@", log: logger, type: .error, error.localizedDescription) os_log("发送命令异常: %@", log: logger, type: .error, error.localizedDescription)
@ -419,23 +503,23 @@ class BleService: NSObject {
} }
} }
/// 计算CRC8/MAXIM校验值 /// 计算CRC8/MAXIM校验值(与Android端保持一致)
private func calculateCRC8(data: Data) -> UInt8 { private func calculateCRC8(data: Data) -> UInt8 {
var crc: UInt8 = 0 var crc: Int = 0
let polynomial: UInt8 = 0x31 let polynomial: Int = 0x31
for byte in data { for byte in data {
crc ^= byte crc = crc ^ (Int(byte) & 0xFF)
for _ in 0..<8 { for _ in 0..<8 {
if (crc & 0x80) != 0 { if (crc & 0x80) != 0 {
crc = ((crc << 1) ^ polynomial) crc = ((crc << 1) ^ polynomial) & 0xFF
} else { } else {
crc = (crc << 1) crc = (crc << 1) & 0xFF
} }
} }
} }
return crc return UInt8(crc & 0xFF)
} }
/// 处理收到的音频数据 /// 处理收到的音频数据
@ -726,9 +810,17 @@ class BleService: NSObject {
notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap) notifyDeviceInfoReceived(infoType: Int(infoType), infoData: resultMap)
case BleConst.CMD_WAKE_UP: case BleConst.CMD_WAKE_UP:
os_log("收到唤醒指令", log: logger, type: .info) os_log("📢 收到唤醒指令", log: logger, type: .info)
os_log("🔍 当前状态检查: 连接状态=%d, writeCharacteristic=%@, notifyCharacteristic=%@, audioCharacteristic=%@",
log: logger, type: .debug,
connectionState,
writeCharacteristic != nil ? "✅" : "❌",
notifyCharacteristic != nil ? "✅" : "❌",
audioCharacteristic != nil ? "✅" : "❌")
// 接收到唤醒信号,打开编码器 (设备侧) // 接收到唤醒信号,打开编码器 (设备侧)
openEncoder() let openResult = openEncoder()
os_log("🎙️ 尝试打开编码器结果: %@", log: logger, type: .info, openResult ? "成功" : "失败")
notifyWakeupSignalReceived() notifyWakeupSignalReceived()
default: default:
@ -745,7 +837,24 @@ class BleService: NSObject {
} }
} }
// MARK: - 资源清理 // MARK: - 通知配置
/// 设置特征的通知功能(与Android端行为一致)
private func setupNotification(for peripheral: CBPeripheral, characteristic: CBCharacteristic) {
// 检查特征是否支持通知
guard characteristic.properties.contains(.notify) else {
os_log("⚠️ 特征不支持通知功能: %{public}@", log: logger, type: .debug, characteristic.uuid.uuidString)
return
}
os_log("🔧 开始配置特征通知: %{public}@", log: logger, type: .debug, characteristic.uuid.uuidString)
// iOS CoreBluetooth会自动处理CLIENT_CHAR_CONFIG_UUID描述符
// 直接使用setNotifyValue即可,不需要手动写入描述符
peripheral.setNotifyValue(true, for: characteristic)
os_log("🔧 特征通知配置完成: %{public}@", log: logger, type: .debug, characteristic.uuid.uuidString)
}
/// 清理所有资源 /// 清理所有资源
func cleanup() { func cleanup() {
@ -756,6 +865,9 @@ class BleService: NSObject {
opusProcessor?.cleanup() opusProcessor?.cleanup()
opusProcessor = nil opusProcessor = nil
// 清理待重置设备列表
devicesToReset = []
os_log("BleService资源已清理", log: logger, type: .debug) os_log("BleService资源已清理", log: logger, type: .debug)
} }
@ -765,29 +877,25 @@ class BleService: NSObject {
func connectToAccessory(uuid: UUID) { func connectToAccessory(uuid: UUID) {
#if canImport(AccessorySetupKit) && swift(>=5.9) #if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
os_log("开始连接设备: %{public}@", log: logger, type: .info, uuid.uuidString)
// 确保CBCentralManager已初始化 // 确保CBCentralManager已初始化
if centralManager == nil { if centralManager == nil {
os_log("CBCentralManager未初始化,开始初始化", log: logger, type: .debug)
initializeCentralManager() initializeCentralManager()
} }
guard let manager = centralManager, manager.state == .poweredOn else { // 将设备添加到连接队列,而不是直接连接
return // 这样即使当前蓝牙状态不是poweredOn,也会在状态变为poweredOn时自动连接
} enqueueConnection(uuid: uuid)
// 使用retrievePeripherals而不是扫描
let peripherals = manager.retrievePeripherals(withIdentifiers: [uuid])
if let peripheral = peripherals.first {
connect(to: peripheral)
}
} }
#endif #endif
} }
} }
// MARK: - OpusAudioProcessor.AudioDataCallback // MARK: - SwiftOpusAudioProcessor.AudioDataCallback
@available(iOS 13.0, *) @available(iOS 13.0, *)
extension BleService: OpusAudioProcessor.AudioDataCallback { extension BleService: SwiftOpusAudioProcessor.AudioDataCallback {
/// 接收到解码后的PCM音频数据 /// 接收到解码后的PCM音频数据
func onAudioDataReceived(data: Data) { func onAudioDataReceived(data: Data) {
notifyAudioDataReceived(data: data) notifyAudioDataReceived(data: data)
@ -797,25 +905,120 @@ extension BleService: OpusAudioProcessor.AudioDataCallback {
// MARK: - CBCentralManagerDelegate // MARK: - CBCentralManagerDelegate
@available(iOS 13.0, *) @available(iOS 13.0, *)
extension BleService: CBCentralManagerDelegate { extension BleService: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) { public func centralManagerDidUpdateState(_ central: CBCentralManager) {
os_log("🔧 CBCentralManager状态更新: %d", log: logger, type: .info, central.state.rawValue)
switch central.state { switch central.state {
case .poweredOn: case .poweredOn:
// 蓝牙已开启,可以开始扫描
break // 蓝牙已开启,可以开始扫描或连接
os_log("🔧 蓝牙已开启 - 这是全新初始化(非状态恢复)", log: logger, type: .info)
// 首先检查是否有需要重置的设备(来自willRestoreState)
if !devicesToReset.isEmpty {
os_log("🔄 蓝牙已就绪,开始重置%d个设备", log: logger, type: .info, devicesToReset.count)
for peripheral in devicesToReset {
os_log("🔄 强制取消设备连接: %{public}@, 状态=%d", log: logger, type: .info,
peripheral.name ?? peripheral.identifier.uuidString, peripheral.state.rawValue)
central.cancelPeripheralConnection(peripheral)
}
// 清空重置列表
devicesToReset = []
os_log("🔄 设备重置完成,继续正常连接流程", log: logger, type: .info)
}
// 检查是否有待连接的设备
tryConnectIfReady()
case .poweredOff: case .poweredOff:
os_log("蓝牙已关闭", log: logger, type: .info)
updateConnectionState(BleConst.STATE_DISCONNECTED) updateConnectionState(BleConst.STATE_DISCONNECTED)
case .resetting, .unauthorized, .unsupported, .unknown: case .resetting:
// 其他状态不处理 os_log("蓝牙正在重置", log: logger, type: .info)
break // 无需额外处理,等待状态变更
case .unauthorized:
os_log("蓝牙权限未授权", log: logger, type: .error)
updateConnectionState(BleConst.STATE_ERROR)
case .unsupported:
os_log("设备不支持蓝牙", log: logger, type: .error)
updateConnectionState(BleConst.STATE_ERROR)
case .unknown:
os_log("蓝牙状态未知", log: logger, type: .info)
// 等待状态变更
@unknown default: @unknown default:
os_log("蓝牙未知状态: %d", log: logger, type: .info, central.state.rawValue)
break break
} }
} }
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { // MARK: - 连接队列处理
// 检查并尝试连接待连接设备
private func tryConnectIfReady() {
os_log("🔍 检查并尝试连接待连接设备", log: logger, type: .debug)
guard let manager = centralManager, manager.state == .poweredOn else {
os_log("蓝牙未就绪,延迟连接", log: logger, type: .info)
return
}
// 防止并发修改
let uuidsToProcess = pendingUUIDs
// 过滤:保留未能成功连接的设备
pendingUUIDs = uuidsToProcess.filter { uuid in
let peripherals = manager.retrievePeripherals(withIdentifiers: [uuid])
if let peripheral = peripherals.first {
// 如果已经连接或正在连接,则不需要再次连接
if peripheral.state == .connected || peripheral.state == .connecting {
os_log("设备已连接或正在连接:state=%d,从队列中移除: %{public}@", log: logger, type: .info, peripheral.state.rawValue, peripheral.name ?? peripheral.identifier.uuidString)
return false // 从队列中移除
}
// 找到外设,尝试连接
os_log("从队列中连接设备: %{public}@", log: logger, type: .info,
peripheral.name ?? peripheral.identifier.uuidString)
// 开始连接
peripheralDevice = peripheral
peripheralDevice?.delegate = self
manager.connect(peripheral, options: [
CBConnectPeripheralOptionNotifyOnConnectionKey: true,
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,
CBConnectPeripheralOptionNotifyOnNotificationKey: true
])
// 启动连接超时检测
startConnectionTimeout()
return false // 连接后从队列中移除
}
// 找不到外设,保留在队列中
return true
}
}
// 添加设备到连接队列
public func enqueueConnection(uuid: UUID) {
if !pendingUUIDs.contains(uuid) {
pendingUUIDs.append(uuid)
os_log("设备已加入连接队列: %{public}@", log: logger, type: .info, uuid.uuidString)
}
// 立即尝试连接
tryConnectIfReady()
}
// 原有方法
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 将扫描结果转换为字典格式 // 将扫描结果转换为字典格式
var deviceInfo: [String: Any] = [ var deviceInfo: [String: Any] = [
"name": peripheral.name ?? "Unknown", "name": peripheral.name ?? "Unknown",
@ -836,9 +1039,17 @@ extension BleService: CBCentralManagerDelegate {
notifyScanResult(devices: [deviceInfo]) notifyScanResult(devices: [deviceInfo])
} }
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
os_log("🔗 BLE设备连接成功: %{public}@", log: logger, type: .info, peripheral.name ?? peripheral.identifier.uuidString)
os_log("🔗 连接详情: UUID=%{public}@, 服务数=%d", log: logger, type: .debug,
peripheral.identifier.uuidString, peripheral.services?.count ?? 0)
// 停止连接超时检测
stopConnectionTimeout()
updateConnectionState(BleConst.STATE_CONNECTED) updateConnectionState(BleConst.STATE_CONNECTED)
os_log("🔍 开始发现服务...", log: logger, type: .debug)
// 查找服务 // 查找服务
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID])
@ -848,78 +1059,139 @@ extension BleService: CBCentralManagerDelegate {
} }
} }
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
let errorMsg = error?.localizedDescription ?? "未知错误"
os_log("❌ BLE设备连接失败: %{public}@, 错误: %{public}@", log: logger, type: .error,
peripheral.name ?? peripheral.identifier.uuidString, errorMsg)
os_log("❌ 失败详情: UUID=%{public}@", log: logger, type: .debug, peripheral.identifier.uuidString)
// 停止连接超时检测
stopConnectionTimeout()
updateConnectionState(BleConst.STATE_ERROR) updateConnectionState(BleConst.STATE_ERROR)
} }
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
if let error = error {
os_log("⚠️ BLE设备意外断开: %{public}@, 错误: %{public}@", log: logger, type: .info,
peripheral.name ?? peripheral.identifier.uuidString, error.localizedDescription)
} else {
os_log("🔌 BLE设备正常断开: %{public}@", log: logger, type: .info,
peripheral.name ?? peripheral.identifier.uuidString)
}
// 停止连接超时检测
stopConnectionTimeout()
updateConnectionState(BleConst.STATE_DISCONNECTED) updateConnectionState(BleConst.STATE_DISCONNECTED)
} }
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
// 从恢复状态中获取之前连接的外设 os_log("🔄 ===== iOS系统正在恢复CBCentralManager状态 =====", log: logger, type: .info)
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral], os_log("🔄 恢复数据: %{public}@", log: logger, type: .debug, dict)
let peripheral = peripherals.first {
peripheralDevice = peripheral // 检查是否有需要恢复的外设
peripheralDevice?.delegate = self if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral], !peripherals.isEmpty {
os_log("🔄 发现%d个需要恢复的设备,记录待重置(等待蓝牙poweredOn)", log: logger, type: .info, peripherals.count)
// 如果之前已连接,更新状态
if peripheral.state == .connected { // 只记录需要重置的设备,不执行任何蓝牙操作(因为此时蓝牙可能还没有poweredOn)
updateConnectionState(BleConst.STATE_CONNECTED) devicesToReset = peripherals
// 重新发现服务
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) for peripheral in peripherals {
} else { os_log("🔄 记录待重置设备: %{public}@, 状态=%d", log: logger, type: .debug,
// 如果之前未连接,尝试重新连接 peripheral.name ?? peripheral.identifier.uuidString, peripheral.state.rawValue)
central.connect(peripheral, options: nil)
} }
// 预先清理应用层状态
peripheralDevice = nil
writeCharacteristic = nil
notifyCharacteristic = nil
audioCharacteristic = nil
updateConnectionState(BleConst.STATE_DISCONNECTED)
os_log("🔄 应用层状态已清理,等待蓝牙poweredOn后执行设备重置", log: logger, type: .info)
} else {
os_log("🔄 没有需要恢复的设备", log: logger, type: .debug)
devicesToReset = []
} }
// 不进行任何蓝牙操作,等待centralManagerDidUpdateState(.poweredOn)
} }
} }
// MARK: - CBPeripheralDelegate // MARK: - CBPeripheralDelegate
@available(iOS 13.0, *) @available(iOS 13.0, *)
extension BleService: CBPeripheralDelegate { extension BleService: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let error = error { if let error = error {
os_log("❌ 服务发现失败: %{public}@", log: logger, type: .error, error.localizedDescription)
return return
} }
guard let services = peripheral.services else { return } guard let services = peripheral.services else {
os_log("❌ 未发现任何服务", log: logger, type: .error)
return
}
os_log("✅ 发现%d个服务", log: logger, type: .info, services.count)
for service in services { for service in services {
if service.uuid == BleConst.PRIMARY_SERVICE_UUID { if service.uuid == BleConst.PRIMARY_SERVICE_UUID {
os_log("🔍 发现主服务,开始发现写入和通知特征...", log: logger, type: .debug)
// 主服务,发现写入和通知特征 // 主服务,发现写入和通知特征
peripheral.discoverCharacteristics([BleConst.WRITE_CHAR_UUID, BleConst.NOTIFY_CHAR_UUID], for: service) peripheral.discoverCharacteristics([BleConst.WRITE_CHAR_UUID, BleConst.NOTIFY_CHAR_UUID], for: service)
} else if service.uuid == BleConst.AUDIO_SERVICE_UUID { } else if service.uuid == BleConst.AUDIO_SERVICE_UUID {
os_log("🔍 发现音频服务,开始发现音频特征...", log: logger, type: .debug)
// 音频服务,发现音频特征 // 音频服务,发现音频特征
peripheral.discoverCharacteristics([BleConst.RECEIVE_AUDIO_CHAR_UUID], for: service) peripheral.discoverCharacteristics([BleConst.RECEIVE_AUDIO_CHAR_UUID], for: service)
} }
} }
} }
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error { if let error = error {
os_log("❌ 特征发现失败: %{public}@", log: logger, type: .error, error.localizedDescription)
return return
} }
guard let characteristics = service.characteristics else { return } guard let characteristics = service.characteristics else {
os_log("❌ 服务%{public}@未发现任何特征", log: logger, type: .error, service.uuid.uuidString)
return
}
os_log("✅ 服务%{public}@发现%d个特征", log: logger, type: .info, service.uuid.uuidString, characteristics.count)
for characteristic in characteristics { for characteristic in characteristics {
if characteristic.uuid == BleConst.WRITE_CHAR_UUID { if characteristic.uuid == BleConst.WRITE_CHAR_UUID {
writeCharacteristic = characteristic writeCharacteristic = characteristic
os_log("📝 写入特征已设置: %{public}@", log: logger, type: .info, characteristic.uuid.uuidString)
} else if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID { } else if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID {
notifyCharacteristic = characteristic notifyCharacteristic = characteristic
// 订阅通知 os_log("🔔 通知特征已设置,开始发现描述符...", log: logger, type: .info)
peripheral.setNotifyValue(true, for: characteristic) // 先发现描述符,再配置通知
peripheral.discoverDescriptors(for: characteristic)
} else if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID { } else if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID {
audioCharacteristic = characteristic audioCharacteristic = characteristic
// 订阅音频通知 os_log("🎵 音频特征已设置,开始发现描述符...", log: logger, type: .info)
peripheral.setNotifyValue(true, for: characteristic) // 先发现描述符,再配置通知
peripheral.discoverDescriptors(for: characteristic)
} }
} }
// 检查所有必要特征是否已准备就绪
let isReady = writeCharacteristic != nil && notifyCharacteristic != nil && audioCharacteristic != nil
if isReady {
os_log("🚀 所有BLE特征已准备就绪,可以发送命令", log: logger, type: .info)
} else {
os_log("⏳ 等待更多特征准备: 写入=%@, 通知=%@, 音频=%@",
log: logger, type: .debug,
writeCharacteristic != nil ? "✅" : "❌",
notifyCharacteristic != nil ? "✅" : "❌",
audioCharacteristic != nil ? "✅" : "❌")
}
} }
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error { if let error = error {
return return
} }
@ -935,11 +1207,49 @@ extension BleService: CBPeripheralDelegate {
} }
} }
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
// 写入完成回调,不需要特殊处理 // 写入完成回调,不需要特殊处理
} }
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
// 通知状态更新回调,不需要特殊处理 // 通知状态更新回调,不需要特殊处理
} }
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
os_log("❌ 描述符发现失败: %{public}@", log: logger, type: .error, error.localizedDescription)
return
}
os_log("✅ 特征%{public}@发现%d个描述符", log: logger, type: .debug,
characteristic.uuid.uuidString, characteristic.descriptors?.count ?? 0)
// 对通知特征和音频特征配置通知
if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID || characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID {
setupNotification(for: peripheral, characteristic: characteristic)
}
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
if let error = error {
os_log("❌ 描述符读取失败: %{public}@", log: logger, type: .error, error.localizedDescription)
return
}
os_log("📖 描述符值已更新: %{public}@", log: logger, type: .debug, descriptor.uuid.uuidString)
}
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
if let error = error {
os_log("❌ 描述符写入失败: %{public}@", log: logger, type: .error, error.localizedDescription)
return
}
if descriptor.uuid == BleConst.CLIENT_CHAR_CONFIG_UUID {
os_log("✅ CLIENT_CHAR_CONFIG_UUID描述符写入成功: %{public}@", log: logger, type: .info,
descriptor.characteristic?.uuid.uuidString ?? "未知特征")
} else {
os_log("✅ 描述符写入成功: %{public}@", log: logger, type: .debug, descriptor.uuid.uuidString)
}
}
} }

128
local_plugins/ble_service/ios/Classes/OpusAudioProcessor.swift

@ -1,128 +0,0 @@
import Foundation
import SpeexKit
import os.log
/// 基于杰理SpeexKit的Opus音频处理器
@available(iOS 13.0, *)
class OpusAudioProcessor: NSObject {
// MARK: - 回调代理协议
protocol AudioDataCallback: AnyObject {
/// 接收到解码后的PCM音频数据
func onAudioDataReceived(data: Data)
}
// MARK: - 属性
private var isDecodingActive = false
private weak var callback: AudioDataCallback?
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "OpusAudioProcessor")
// MARK: - 初始化
override init() {
super.init()
// 注册通知 - 这是SDK设计要求的
NotificationCenter.default.addObserver(
self,
selector: #selector(onOpusDecodedData(_:)),
name: NSNotification.Name(rawValue: kOPUS_DECODE_DATA),
object: nil
)
// 启用日志
OpusUnit.opusIsLog(true)
}
deinit {
stopDecoding()
NotificationCenter.default.removeObserver(self)
}
// MARK: - 通知回调
@objc private func onOpusDecodedData(_ notification: Notification) {
guard let pcmData = notification.object as? Data else {
os_log("解码回调收到无效数据", log: logger, type: .error)
return
}
// 传递给回调
DispatchQueue.main.async { [weak self] in
self?.callback?.onAudioDataReceived(data: pcmData)
}
}
// MARK: - 公共接口
/// 设置回调
func setCallback(_ callback: AudioDataCallback?) {
self.callback = callback
}
/// 开始Opus解码
func startDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 640) -> Bool {
// 如果已经在解码,直接返回成功
if isDecodingActive {
os_log("Opus解码器已经在运行", log: logger, type: .debug)
return true
}
// 设置音频参数
OpusUnit.opusSetSampleRate(Int32(sampleRate), kbps: 16000, channels: Int32(channels))
// 必须在后台线程启动解码器(SDK要求)
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
// 启动解码器
let result = OpusUnit.opusDecoderRun()
if result == 0 {
os_log("Opus解码器启动成功: 采样率=%d Hz", log: self.logger, type: .info, sampleRate)
self.isDecodingActive = true
} else {
os_log("Opus解码器启动失败: 错误=%d", log: self.logger, type: .error, result)
}
}
return true
}
/// 停止Opus解码
func stopDecoding() -> Bool {
if !isDecodingActive {
return false
}
// 必须在后台线程停止解码器
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
let result = OpusUnit.opusDecoderStop()
os_log("Opus解码器停止: %d", log: self.logger, type: .info, result)
self.isDecodingActive = false
}
return true
}
/// 处理Opus编码数据
func processAudioData(_ data: Data) {
if !isDecodingActive {
os_log("Opus解码器未启动", log: logger, type: .error)
return
}
// 将Opus数据送入解码器
OpusUnit.opusWrite(data)
// 解码结果将通过通知回调给onOpusDecodedData方法
}
/// 判断解码器是否运行中
func isDecoding() -> Bool {
return isDecodingActive
}
/// 清理资源
func cleanup() {
stopDecoding()
}
}

184
local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift

@ -21,7 +21,8 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
#if canImport(AccessorySetupKit) && swift(>=5.9) #if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
// iOS 18环境下,先只初始化AccessorySetup
// iOS 18环境下,只设置回调但不主动初始化会话
let bleAccessorySetup = BleAccessorySetup.shared let bleAccessorySetup = BleAccessorySetup.shared
// 在accessoryAdded回调中延迟设置BleService // 在accessoryAdded回调中延迟设置BleService
@ -32,8 +33,10 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
self?.handleDiscoveredDevice(uuid: uuid) self?.handleDiscoveredDevice(uuid: uuid)
} }
// 初始化发现会话 // 注释掉自动初始化,改为等待用户主动调用startDeviceAssociation
bleAccessorySetup.initializeDiscoverySession() // bleAccessorySetup.initializeSession()
os_log("iOS 18环境:已设置设备发现回调,等待用户主动调用关联", log: logger, type: .info)
} else { } else {
// 低于iOS 18,直接初始化所有服务 // 低于iOS 18,直接初始化所有服务
initializeAllServices() initializeAllServices()
@ -43,6 +46,7 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
initializeAllServices() initializeAllServices()
#endif #endif
} }
// 确保所有服务都已初始化 // 确保所有服务都已初始化
private func ensureServicesInitialized() { private func ensureServicesInitialized() {
@ -62,13 +66,15 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
// 设置BLE服务回调 // 设置BLE服务回调
BleService.shared.addCallback(self) BleService.shared.addCallback(self)
// 初始化AccessorySetup // 初始化AccessorySetup,但不自动开始会话
BleAccessorySetup.shared.setBleService(BleService.shared) BleAccessorySetup.shared.setBleService(BleService.shared)
BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in
self?.handleDiscoveredDevice(uuid: uuid) self?.handleDiscoveredDevice(uuid: uuid)
} }
BleAccessorySetup.shared.initializeDiscoverySession() // 移除自动初始化,统一由startDeviceAssociation方法触发
// BleAccessorySetup.shared.initializeSession()
os_log("非iOS 18环境:已设置BLE服务但不自动初始化会话", log: logger, type: .info)
servicesInitialized = true servicesInitialized = true
} }
@ -116,10 +122,11 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
#endif #endif
case "getPairedMacAddress": case "getPairedMacAddress":
// iOS上禁用AccessorySetupKit,始终返回无配对设备 // 在新的实现中我们不再直接提供已配对设备
os_log("iOS版本使用AccessorySetupKit自动管理配对", log: logger, type: .debug)
result(nil) result(nil)
case "registerBackgroundScan", "unregisterBackgroundScan", "clearAssociations": case "registerBackgroundScan", "unregisterBackgroundScan":
// iOS上禁用,直接返回成功以避免Flutter端报错 // iOS上禁用,直接返回成功以避免Flutter端报错
result(true) result(true)
@ -148,10 +155,12 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
result(BleService.shared.playA2DP()) result(BleService.shared.playA2DP())
// iOS 18+ AccessorySetupKit相关方法 // iOS 18+ AccessorySetupKit相关方法
case "startDeviceAssociation": case "clearAssociations":
// 清除设备配对关系
#if canImport(AccessorySetupKit) && swift(>=5.9) #if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
showDevicePicker() os_log("清除设备配对关系", log: logger, type: .info)
BleAccessorySetup.shared.clearAssociations()
result(true) result(true)
} else { } else {
result(true) result(true)
@ -160,6 +169,29 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
result(true) result(true)
#endif #endif
case "startDeviceAssociation":
// 用户主动触发设备关联流程
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
os_log("用户主动触发设备关联流程", log: logger, type: .info)
// 确保服务已初始化
ensureServicesInitialized()
// 重新初始化会话
BleAccessorySetup.shared.initializeSession()
// 返回成功,实际配对结果会通过回调通知
result(true)
} else {
os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info)
result(false)
}
#else
os_log("当前环境不支持AccessorySetupKit", log: logger, type: .info)
result(false)
#endif
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }
@ -175,27 +207,11 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
#endif #endif
} }
// 显示设备选择器
private func showDevicePicker() {
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
// 从主线程调用
DispatchQueue.main.async {
// 获取当前活跃的视图控制器
if let rootVC = UIApplication.shared.windows.first?.rootViewController {
let topVC = self.getTopViewController(rootVC)
BleAccessorySetup.shared.showDevicePickerFromViewController(topVC)
} else {
BleAccessorySetup.shared.safeShowDevicePicker()
}
}
}
#endif
}
// AccessorySetupKit发现设备的处理 // AccessorySetupKit发现设备的处理
private func handleDiscoveredDevice(uuid: UUID) { private func handleDiscoveredDevice(uuid: UUID) {
BleAccessorySetup.shared.handleDiscoveredAccessory(uuid: uuid) // BleAccessorySetup内部已进行连接,此处不再重复调用
// os_log("收到设备发现回调: %{public}@", log: logger, type: .info, uuid.uuidString)
// 可以在这里添加设备记录或其他操作
} }
private func sendEvent<T>(_ eventSink: FlutterEventSink?, data: T, errorPrefix: String) { private func sendEvent<T>(_ eventSink: FlutterEventSink?, data: T, errorPrefix: String) {
@ -269,67 +285,75 @@ extension SwiftBleServicePlugin: FlutterStreamHandler {
// MARK: - BleService.Callback // MARK: - BleService.Callback
@available(iOS 13.0, *) @available(iOS 13.0, *)
extension SwiftBleServicePlugin: BleService.Callback { extension SwiftBleServicePlugin: BleService.Callback {
func onScanResult(devices: [[String : Any]]) { public func onScanResult(devices: [[String: Any]]) {
sendEvent(statusEventSink, if let eventSink = statusEventSink {
data: ["type": "scanResult", "devices": devices], DispatchQueue.main.async {
errorPrefix: "发送扫描结果异常") eventSink([
"event": "scanResult",
"devices": devices
])
}
}
} }
func onConnectionStateChanged(state: Int) { public func onConnectionStateChanged(state: Int) {
var stateStr = "Unknown" if let eventSink = statusEventSink {
switch state { var stateDesc = "未知"
case BleConst.STATE_DISCONNECTED: switch state {
stateStr = "Disconnected" case BleConst.STATE_DISCONNECTED:
case BleConst.STATE_CONNECTING: stateDesc = "已断开"
stateStr = "Connecting" case BleConst.STATE_CONNECTING:
case BleConst.STATE_CONNECTED: stateDesc = "连接中"
stateStr = "Connected" case BleConst.STATE_CONNECTED:
case BleConst.STATE_DISCONNECTING: stateDesc = "已连接"
stateStr = "Disconnecting" case BleConst.STATE_DISCONNECTING:
case BleConst.STATE_ERROR: stateDesc = "断开中"
stateStr = "Connection Error" case BleConst.STATE_ERROR:
default: stateDesc = "错误"
stateStr = "Unknown State(\(state))" default:
break
}
DispatchQueue.main.async {
eventSink([
"event": "connectionStateChanged",
"state": state,
"stateDesc": stateDesc
])
}
} }
let stateMap: [String: Any] = [
"type": "connectionStateChanged",
"state": state,
"stateStr": stateStr
]
sendEvent(statusEventSink, data: stateMap, errorPrefix: "发送连接状态异常")
} }
func onAudioDataReceived(data: Data) { public func onAudioDataReceived(data: Data) {
let dataMap: [String: Any] = [ if let eventSink = dataEventSink {
"type": "audioData", DispatchQueue.main.async {
"data": data eventSink([
] "event": "audioDataReceived",
"data": FlutterStandardTypedData(bytes: data)
sendEvent(dataEventSink, data: dataMap, errorPrefix: "发送音频数据异常") ])
}
}
} }
func onWakeupSignalReceived() { public func onWakeupSignalReceived() {
let wakeupMap: [String: Any] = [ if let eventSink = statusEventSink {
"type": "wakeupSignal", DispatchQueue.main.async {
"timestamp": Date().timeIntervalSince1970 * 1000 eventSink([
] "event": "wakeupSignalReceived"
])
sendEvent(statusEventSink, data: wakeupMap, errorPrefix: "发送唤醒信号异常") }
}
} }
func onDeviceInfoReceived(infoType: Int, infoData: [String : Any]) { public func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) {
var dataMap: [String: Any] = [ if let eventSink = statusEventSink {
"type": "deviceInfo", DispatchQueue.main.async {
"infoType": infoType eventSink([
] "event": "deviceInfoReceived",
"infoType": infoType,
// 合并infoData "infoData": infoData
for (key, value) in infoData { ])
dataMap[key] = value }
} }
sendEvent(statusEventSink, data: dataMap, errorPrefix: "发送设备信息异常")
} }
} }

151
local_plugins/ble_service/ios/Classes/SwiftOpusAudioProcessor.swift

@ -0,0 +1,151 @@
import Foundation
import AVFoundation
import YbridOpus
@available(iOS 13.0, *)
class SwiftOpusAudioProcessor: NSObject {
protocol AudioDataCallback: AnyObject {
func onAudioDataReceived(data: Data)
}
private var isDecodingActive = false
private weak var callback: AudioDataCallback?
// Opus 解码器相关 - 使用 YbridOpus C API
private var decoder: OpaquePointer?
private var sampleRate: Int32 = 16000
private var channels: Int32 = 1
private let maxFrameSize: Int32 = 5760 // 120 ms @48 kHz 的最大值
override init() {
super.init()
}
deinit {
cleanup()
}
private func initializeOpusDecoder() -> Bool {
if decoder != nil {
return true
}
var error: Int32 = 0
decoder = opus_decoder_create(sampleRate, channels, &error)
guard error == OPUS_OK, decoder != nil else {
return false
}
return true
}
private func destroyOpusDecoder() {
if let decoder = decoder {
opus_decoder_destroy(decoder)
self.decoder = nil
}
}
func setCallback(_ callback: AudioDataCallback?) {
self.callback = callback
}
func startDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 640) -> Bool {
if isDecodingActive {
return true
}
self.sampleRate = sampleRate
self.channels = channels
guard initializeOpusDecoder() else {
return false
}
isDecodingActive = true
return true
}
func stopDecoding() -> Bool {
if !isDecodingActive {
return false
}
isDecodingActive = false
destroyOpusDecoder()
return true
}
func processAudioData(_ data: Data) {
guard isDecodingActive, let decoder = decoder, data.count > 0 else {
return
}
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.decodeOpusPacket(data)
}
}
// 使用简单稳定的 Opus 解码方法
private func decodeOpusPacket(_ packet: Data) {
guard let decoder = decoder else { return }
// 使用简单的直接解码方式
let maxSamples = Int(maxFrameSize)
var pcmBuffer = [opus_int16](repeating: 0, count: maxSamples * Int(channels))
let decodedSamples = packet.withUnsafeBytes { packetPtr in
let uint8Ptr = packetPtr.bindMemory(to: UInt8.self).baseAddress
return opus_decode(
decoder,
uint8Ptr,
opus_int32(packet.count),
&pcmBuffer,
Int32(maxSamples),
0
)
}
guard decodedSamples > 0 else {
return
}
// 提取解码的音频数据
let samplesPerChannel = Int(decodedSamples)
let totalSamples = samplesPerChannel * Int(channels)
let outputPCM = Array(pcmBuffer[0..<totalSamples])
if !outputPCM.isEmpty {
processPCMData(outputPCM)
}
}
private func processPCMData(_ pcmData: [opus_int16]) {
// 转换为 Data
let data = Data(bytes: pcmData, count: pcmData.count * MemoryLayout<opus_int16>.size)
// 回调通知
DispatchQueue.main.async { [weak self] in
self?.callback?.onAudioDataReceived(data: data)
}
}
func isDecoding() -> Bool {
return isDecodingActive
}
func cleanup() {
stopDecoding()
}
// MARK: - 公共接口
/// 获取当前配置信息
func getCurrentConfig() -> (sampleRate: Int32, channels: Int32) {
return (sampleRate, channels)
}
}

92
local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Headers/OpusUnit.h

@ -1,92 +0,0 @@
//
// OpusUnit.h
// QCY_Demo
//
// Created by 杰理科技 on 2021/7/5.
// Copyright © 2021 杰理科技. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 通知:流式编码回调Opus数据
* 通知数据类型:NSData(Opus)
*/
extern NSString *kOPUS_ENCODE_DATA;
/**
* 通知:流式解码回调PCM数据
* 通知数据类型:NSData(PCM)
*/
extern NSString *kOPUS_DECODE_DATA;
@interface OpusUnit : NSObject
+ (void)opusIsLog:(BOOL)log;
#pragma mark - Opus参数
/**
* 设置参数
* @param rate 采样率 例如:48000、24000、16000、12000、8000(默认 16000)
* @param kbps 比特率 例如:16000、32000、64000(默认 16000)
* @param channel 声道 例如:1、2 (默认 1)
*/
+(void)opusSetSampleRate:(int)rate Kbps:(int)kbps Channels:(int)channel;
#pragma mark - Opus解码
/**
* 直接【opus文件】转换成【pcm文件】
* @param path_opus opus文件路径
* @param path_pcm pcm文件路径
*/
+ (int)opusDecodeOPUS:(NSString *)path_opus PCM:(NSString *)path_pcm;
/**
* 流式解码【开启】
*/
+ (int)opusDecoderRun;
/**
* 输入Opus的数据
* 通知监听“kOPUS_DECODE_DATA”获得解码后数据
* 通知数据类型:NSNotification.object => NSData(PCM格式)
* @param data opus数据流(长度:1024)
*/
+ (void)opusWriteData:(NSData*)data;
/**
* 流式解码【关闭】
*/
+ (int)opusDecoderStop;
#pragma mark - Opus编码
/**
* 【pcm文件】转换成【opus文件】
*/
+ (int)opusEncodePCM:(NSString *)path_pcm OPUS:(NSString *)path_opus;
/**
* 流式编码【开启】
*/
+ (int)opusEecoderRun;
/**
* 输入pcm的数据
* 通知监听“kOPUS_DECODE_DATA”获得解码后数据
* 通知数据类型:NSNotification.object => NSData(opus格式)
* @param data opus数据流(长度:1024)
*/
+ (void)pcmWriteData:(NSData*)data;
/**
* 流式编码【关闭】
*/
+ (int)opusEncoderStop;
@end
NS_ASSUME_NONNULL_END

20
local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Headers/SpeexKit.h

@ -1,20 +0,0 @@
//
// SpeexKit.h
// SpeexKit
//
// Created by zhihui liang on 2018/8/20.
// Copyright © 2018年 www.zh-jieli.com. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for SpeexKit.
FOUNDATION_EXPORT double SpeexKitVersionNumber;
//! Project version string for SpeexKit.
FOUNDATION_EXPORT const unsigned char SpeexKitVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SpeexKit/PublicHeader.h>
#import <SpeexKit/SpeexUnit.h>
#import <SpeexKit/OpusUnit.h>

40
local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Headers/SpeexUnit.h

@ -1,40 +0,0 @@
//
// SpeexUnit.h
// SpeexKit
//
// Created by zhihui liang on 2018/8/20.
// Copyright © 2018年 www.zh-jieli.com. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 流式解码回调PCM数据
*/
extern NSString *kSPEEX_DECODE_DATA; //speex decode
@interface SpeexUnit : NSObject
+(void)speexIsLog:(BOOL)log;
/**
* 直接【speex文件】转换成【pcm文件】
*/
+(int)speexDecodeSPX:(NSString *)path_spx PCM:(NSString *)path_pcm;
/**
* 流式解码【开启】
*/
+(int)speexDecoderRun;
/**
* 输入Speex的数据
*/
+(void)speexWriteData:(NSData*)data;
/**
* 流式解码【关闭】
*/
+(int)speexDecoderStop;
@end

BIN
local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Info.plist

Binary file not shown.

6
local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/Modules/module.modulemap

@ -1,6 +0,0 @@
framework module SpeexKit {
umbrella header "SpeexKit.h"
export *
module * { export * }
}

BIN
local_plugins/ble_service/ios/Frameworks/SpeexKit.framework/SpeexKit

Binary file not shown.

8
local_plugins/ble_service/ios/ble_service.podspec

@ -17,12 +17,12 @@ BLE服务插件,提供蓝牙扫描、连接和通信功能,支持iOS 18+ Acc
s.dependency 'Flutter' s.dependency 'Flutter'
s.platform = :ios, '18.0' s.platform = :ios, '18.0'
# -- ① 把 SpeexKit 声明为“随包带来的二进制框架” -- # YbridOpus 依赖 (Swift Opus 封装)
s.vendored_frameworks = 'Frameworks/SpeexKit.framework' # <- 或 .xcframework s.dependency 'YbridOpus', '~> 0.8'
# -- ② SpeexKit 可能依赖到的系统库 -- # 系统框架依赖
s.frameworks = 'AVFoundation', 'AudioToolbox' s.frameworks = 'AVFoundation', 'AudioToolbox'
s.libraries = 'iconv', 'c++' # libc++/iconv多数 Opus/Speex 都要 s.libraries = 'iconv', 'c++'
# Flutter.framework does not contain a i386 slice. # Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }

126
local_plugins/jl_opus/README.md

@ -1,126 +0,0 @@
# JL OPUS Plugin
基于杰理音频编解码库的Flutter插件,提供OPUS音频格式的解码功能。
## 功能
- 支持OPUS文件解码为PCM格式
- 支持OPUS数据流解码
- 支持多种采样率和通道配置
- 支持带协议头/不带协议头的OPUS数据
## 引入方式
在pubspec.yaml中添加依赖:
```yaml
dependencies:
jl_opus:
path: ./local_plugins/jl_opus
```
## 使用方法
### 初始化插件
```dart
import 'package:jl_opus/jl_opus.dart';
final jlOpus = JlOpus();
```
### 解码OPUS文件
```dart
Future<void> decodeOpusFile() async {
final inPath = '/path/to/input.opus';
final outPath = '/path/to/output.pcm';
// 创建解码选项
final option = OpusOption(
hasHeader: false, // 是否有协议头
channel: 1, // 通道数:1=单声道,2=立体声
sampleRate: 16000, // 采样率
packetSize: 40, // 数据包大小(仅当hasHeader=false时生效)
);
try {
// 开始解码文件
final outFilePath = await jlOpus.decodeOpusFile(inPath, outPath, option);
print('解码成功: $outFilePath');
} catch (e) {
print('解码失败: $e');
}
}
```
### 解码OPUS数据流
```dart
Future<void> decodeOpusStream() async {
// 监听解码后的PCM数据
jlOpus.decodedDataStream.listen((pcmData) {
print('收到解码后的PCM数据: ${pcmData.length} 字节');
// 处理解码后的PCM数据
});
// 创建解码选项
final option = OpusOption(
hasHeader: false,
channel: 1,
sampleRate: 16000,
packetSize: 40,
);
// 开始解码数据流
final success = await jlOpus.startDecodeStream(option);
if (success) {
print('开始解码数据流');
// 写入OPUS数据进行解码
final opusData = Uint8List.fromList([/* OPUS数据 */]);
await jlOpus.writeAudioStream(opusData);
// 数据处理完成后停止解码
await jlOpus.stopDecodeStream();
}
}
```
### 监听事件
```dart
void listenToEvents() {
jlOpus.eventStream.listen((event) {
switch (event.event) {
case 'onStart':
print('开始${event.type == "file" ? "文件" : "流"}解码');
break;
case 'onComplete':
print('完成${event.type == "file" ? "文件" : "流"}解码');
if (event.filePath != null) {
print('输出文件: ${event.filePath}');
}
break;
case 'onError':
print('解码错误: [${event.errorCode}] ${event.errorMessage}');
break;
}
});
}
```
### 资源释放
```dart
Future<void> disposePlugin() async {
await jlOpus.dispose();
print('插件资源已释放');
}
```
## 注意事项
1. OPUS解码需要正确设置参数,特别是采样率和通道数必须与原始音频匹配
2. 使用前请确保jl_audio_decode_V2.0.0_20005.aar包已添加到libs目录
3. 本插件仅支持Android平台

62
local_plugins/jl_opus/android/build.gradle.kts

@ -1,62 +0,0 @@
plugins {
// Android Library 插件
id("com.android.library")
// Kotlin Android 插件
id("org.jetbrains.kotlin.android")
id("com.github.johnrengelman.shadow") version "8.1.1"
}
// 添加mavenLocal()仓库
repositories {
mavenLocal()
google()
mavenCentral()
}
android {
// 命名空间,对应你插件的包名(需与代码内包名保持一致)
namespace = "com.yunqiinnovation.jl_opus"
// 目标 SDK 版本
compileSdk = 35
defaultConfig {
// 最低 SDK 版本
minSdk = 21
targetSdk = 33
}
// Java 语言级别兼容配置
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
// Kotlin 语言级别
kotlinOptions {
jvmTarget = "11"
}
// 添加packaging配置,排除冲突文件
packaging {
resources {
excludes.add("META-INF/DEPENDENCIES")
excludes.add("META-INF/LICENSE")
excludes.add("META-INF/LICENSE.txt")
excludes.add("META-INF/license.txt")
excludes.add("META-INF/NOTICE")
excludes.add("META-INF/NOTICE.txt")
excludes.add("META-INF/notice.txt")
excludes.add("META-INF/*.kotlin_module")
}
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10")
implementation("androidx.annotation:annotation:1.5.0")
implementation("androidx.core:core-ktx:1.9.0")
// 使用从本地Maven仓库引用的AAR依赖
implementation("com.jieli.audio:jl_audio_decode:2.0.0")
}

1
local_plugins/jl_opus/android/settings.gradle.kts

@ -1 +0,0 @@
rootProject.name = "jl_opus"

9
local_plugins/jl_opus/android/src/main/AndroidManifest.xml

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yunqiinnovation.jl_opus">
<!-- 外部存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
</manifest>

426
local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

@ -1,426 +0,0 @@
package com.yunqiinnovation.jl_opus
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull
import com.jieli.jl_audio_decode.opus.model.OpusOption
import com.jieli.jl_audio_decode.callback.OnDecodeStreamCallback
import com.jieli.jl_audio_decode.callback.OnStateCallback
import com.jieli.jl_audio_decode.opus.OpusManager
import com.jieli.jl_audio_decode.exceptions.OpusException
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.io.File
/** JlOpusPlugin */
class JlOpusPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var eventChannel: EventChannel
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 CallbackOpus {
fun onAudioDataReceivedOpus(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
}
override fun onCancel(arguments: Any?) {
streamSink = null
}
})
// 初始化OpusManager
try {
opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功")
} catch (e: OpusException) {
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e)
}
}
// 在主线程上安全发送事件
private fun sendEventOnMainThread(event: Map<String, Any?>) {
if (streamSink == null) return
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接发送
streamSink?.success(event)
} else {
// 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
}
}
// 在主线程上安全返回结果
private fun sendResultOnMainThread(result: Result, value: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.success(value)
} else {
// 否则切换到主线程
mainHandler.post {
result.success(value)
}
}
}
// 在主线程上安全返回错误
private fun sendErrorOnMainThread(
result: Result,
errorCode: String,
errorMessage: String,
errorDetails: Any?
) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else {
// 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
}
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (opusManager == null) {
result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null)
return
}
when (call.method) {
"initOpusDecoder" -> {
result.success(opusManager != null)
}
"decodeOpusFile" -> {
val inPath = call.argument<String>("inPath")
val outPath = call.argument<String>("outPath")
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
if (inPath == null || outPath == null) {
result.error("INVALID_ARGS", "输入或输出路径不能为空", null)
return
}
// 确保输入文件存在
val inFile = File(inPath)
if (!inFile.exists()) {
result.error("FILE_NOT_FOUND", "输入文件不存在: $inPath", null)
return
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码文件, 参数: $option, 输入: $inPath, 输出: $outPath")
opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback {
override fun onStart() {
Log.d(TAG, "文件解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "file"
)
)
}
override fun onComplete(outFilePath: String?) {
Log.d(TAG, "文件解码完成: $outFilePath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "file",
"filePath" to outFilePath
)
)
sendResultOnMainThread(result, outFilePath)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "文件解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "file",
"code" to code,
"message" to message
)
)
sendErrorOnMainThread(
result,
"DECODE_ERROR",
"解码出错: [$code] $message",
null
)
}
})
}
"startDecodeStream" -> {
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
// 如果已经在解码流,先停止
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) {
sendEventOnMainThread(
mapOf(
"event" to "onDecodeStream",
"data" to data
)
)
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "stream"
)
)
sendResultOnMainThread(result, true)
}
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
)
)
sendErrorOnMainThread(
result,
"DECODE_STREAM_ERROR",
"数据流解码出错: [$code] $message",
null
)
}
})
}
"stopDecodeStream" -> {
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
result.success(true)
} else {
result.success(false)
}
}
"writeAudioStream" -> {
val data = call.argument<ByteArray>("data")
if (data == null) {
result.error("INVALID_ARGS", "音频数据不能为空", null)
return
}
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(data)
result.success(true)
} else {
result.error("NOT_DECODING", "当前没有处于解码状态", null)
}
}
"isDecoding" -> {
result.success(opusManager?.isDecodeStream == true)
}
"dispose" -> {
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
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: CallbackOpus
) {
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.onAudioDataReceivedOpus(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 {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
}
}

BIN
local_plugins/jl_opus/android/src/main/libs/jl_audio_decode_V2.0.0_20005.aar

Binary file not shown.

221
local_plugins/jl_opus/lib/jl_opus.dart

@ -1,221 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';
/// OPUS解码参数选项
class OpusOption {
/// 是否具有协议头
final bool hasHeader;
/// 通道数。取值范围:[1, 2]
final int channel;
/// 采样率。参考值:{8000, 16000, 24000, 32000, 44100, 48000}
final int sampleRate;
/// 数据包长度。仅hasHeader = false时生效。
final int packetSize;
/// 构造函数
OpusOption({
this.hasHeader = false,
this.channel = 1,
this.sampleRate = 16000,
this.packetSize = 40,
});
/// 转换为参数Map
Map<String, dynamic> toMap() {
return {
'hasHeader': hasHeader,
'channel': channel,
'sampleRate': sampleRate,
'packetSize': packetSize,
};
}
}
/// 杰理OPUS编解码器事件
class OpusEvent {
/// 事件类型
final String event;
/// 数据类型 (file或stream)
final String? type;
/// 解码后的数据 (仅在流解码时有效)
final Uint8List? data;
/// 输出文件路径 (仅在文件解码完成时有效)
final String? filePath;
/// 错误码 (仅在发生错误时有效)
final int? errorCode;
/// 错误信息 (仅在发生错误时有效)
final String? errorMessage;
/// 构造函数
OpusEvent({
required this.event,
this.type,
this.data,
this.filePath,
this.errorCode,
this.errorMessage,
});
/// 从Map构造对象
factory OpusEvent.fromMap(Map<dynamic, dynamic> map) {
return OpusEvent(
event: map['event'],
type: map['type'],
data: map['data'],
filePath: map['filePath'],
errorCode: map['code'],
errorMessage: map['message'],
);
}
}
/// 杰理OPUS编解码插件
class JlOpus {
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<OpusEvent>? _eventStream;
/// 获取事件流
Stream<OpusEvent> get eventStream {
_eventStream ??= _eventChannel.receiveBroadcastStream().map((event) {
if (event is Map) {
return OpusEvent.fromMap(event);
}
throw PlatformException(
code: 'INVALID_EVENT',
message: '无效的事件数据',
);
});
return _eventStream!;
}
/// 解码后的数据流
final StreamController<Uint8List> _decodedDataController =
StreamController<Uint8List>.broadcast();
/// 获取解码数据流
Stream<Uint8List> get decodedDataStream => _decodedDataController.stream;
/// 构造函数,设置事件监听
JlOpus() {
_setupEventListener();
}
/// 设置事件监听
void _setupEventListener() {
eventStream.listen((event) {
if (event.event == 'onDecodeStream' && event.data != null) {
_decodedDataController.add(event.data!);
}
});
}
/// 初始化OPUS解码器
Future<bool> initOpusDecoder() async {
try {
final result = await _methodChannel.invokeMethod<bool>('initOpusDecoder');
return result ?? false;
} catch (e) {
return false;
}
}
/// 解码OPUS文件
///
/// [inPath] 输入文件路径
/// [outPath] 输出文件路径
/// [option] 解码参数
Future<String?> decodeOpusFile(
String inPath, String outPath, OpusOption option) async {
try {
final result = await _methodChannel.invokeMethod<String>(
'decodeOpusFile',
{
'inPath': inPath,
'outPath': outPath,
...option.toMap(),
},
);
return result;
} catch (e) {
rethrow;
}
}
/// 开始解码OPUS数据流
///
/// [option] 解码参数
Future<bool> startDecodeStream(OpusOption option) async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'startDecodeStream',
option.toMap(),
);
return result ?? false;
} catch (e) {
rethrow;
}
}
/// 停止解码OPUS数据流
Future<bool> stopDecodeStream() async {
try {
final result =
await _methodChannel.invokeMethod<bool>('stopDecodeStream');
return result ?? false;
} catch (e) {
return false;
}
}
/// 写入OPUS音频数据进行解码
///
/// [data] OPUS音频数据
Future<bool> writeAudioStream(Uint8List data) async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'writeAudioStream',
{'data': data},
);
return result ?? false;
} catch (e) {
rethrow;
}
}
/// 判断是否正在解码
Future<bool> isDecoding() async {
try {
final result = await _methodChannel.invokeMethod<bool>('isDecoding');
return result ?? false;
} catch (e) {
return false;
}
}
/// 释放资源
Future<bool> dispose() async {
try {
_decodedDataController.close();
final result = await _methodChannel.invokeMethod<bool>('dispose');
return result ?? false;
} catch (e) {
return false;
}
}
}

25
local_plugins/jl_opus/pubspec.yaml

@ -1,25 +0,0 @@
name: jl_opus
description: 杰理OPUS编解码库Flutter插件
version: 0.0.1
homepage:
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
path_provider: ^2.0.0
permission_handler: ^11.4.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
plugin:
platforms:
android:
package: com.yunqiinnovation.jl_opus
pluginClass: JlOpusPlugin

253
local_plugins/open_ai_service/README.md

@ -1,253 +0,0 @@
# OpenAI Service Plugin
一个用于Flutter应用的OpenAI服务插件,支持Android和iOS平台。
## 功能
- 支持文本生成(completions)
- 支持流式输出(streaming)
- 支持函数调用(function calling)
- 支持自定义API基础URL
- 支持自定义模型选择
## 安装
在你的`pubspec.yaml`文件中添加以下依赖:
```yaml
dependencies:
open_ai_service:
path: 本地路径/open_ai_service
```
## 使用方法
### 初始化服务
```dart
import 'package:open_ai_service/open_ai_service.dart';
final openAIService = OpenAIService();
// 初始化服务
await openAIService.initialize(
apiKey: 'your_openai_api_key',
baseUrl: 'https://api.openai.com/v1/chat/completions', // 可选
model: 'gpt-4-turbo', // 可选
);
```
### 发送非流式请求
```dart
// 创建消息
final userMessage = await openAIService.createUserMessage('你好,请介绍一下自己');
// 发送请求
final response = await openAIService.sendMessage(
messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
);
print('AI回复: $response');
```
### 发送流式请求(回调方式)
```dart
// 创建消息
final userMessage = await openAIService.createUserMessage('写一个短故事');
// 发送流式请求
await openAIService.sendMessageStream(
messages: [userMessage],
systemPrompt: '你是一个善于讲故事的AI助手',
);
// 处理事件
final subscription = openAIService.processEvents(
onToken: (token) {
// 处理每个返回的token
print(token);
},
onComplete: () {
// 处理完成事件
print('生成完成');
},
onError: (error) {
// 处理错误
print('错误: $error');
},
onFunctionCall: (functionCall) {
// 处理函数调用
print('函数调用: ${functionCall['name']}');
},
);
// 在不需要时取消订阅
subscription.cancel();
```
### 发送流式请求(Stream方式)
```dart
// 创建消息
final userMessage = await openAIService.createUserMessage('写一个短故事');
// 获取字符串流
final stream = openAIService.streamMessage(
messages: [userMessage],
systemPrompt: '你是一个善于讲故事的AI助手',
);
// 使用流
final StringBuilder responseBuilder = StringBuilder();
stream.listen(
(token) {
// 处理每个token
responseBuilder.write(token);
print(token); // 实时输出
},
onDone: () {
// 流结束
print('完整回复: ${responseBuilder.toString()}');
},
onError: (error) {
// 错误处理
print('错误: $error');
}
);
```
### 注册函数
```dart
// 注册一个函数
await openAIService.registerFunction(
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: {
'type': 'object',
'properties': {
'city': {
'type': 'string',
'description': '城市名称',
},
'date': {
'type': 'string',
'description': '日期,格式为YYYY-MM-DD',
},
},
'required': ['city'],
},
);
```
### 处理函数调用
```dart
// 创建消息
final userMessage = await openAIService.createUserMessage('明天北京的天气如何?');
// 发送流式请求
await openAIService.sendMessageStream(
messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
);
// 处理事件
openAIService.processEvents(
onToken: (token) {
print(token);
},
onComplete: () {
print('生成完成');
},
onError: (error) {
print('错误: $error');
},
onFunctionCall: (functionCall) {
// 处理函数调用
final name = functionCall['name'];
final arguments = functionCall['arguments'];
print('收到函数调用: $name, 参数: $arguments');
// 假设处理了函数调用并获得结果
final result = '{"temperature": 25, "condition": "sunny"}';
// 发送函数调用结果
openAIService.sendFunctionCallResult(
messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
functionCall: functionCall,
functionResult: result,
);
},
);
```
### 使用Stream API处理函数调用
```dart
// 创建消息和响应处理器
final userMessage = await openAIService.createUserMessage('明天北京的天气如何?');
final responseBuilder = StringBuilder();
// 处理事件流以捕获函数调用
final subscription = openAIService.processEvents(
onFunctionCall: (functionCall) async {
// 取消当前事件监听
subscription.cancel();
// 处理函数调用
final name = functionCall['name'];
final arguments = functionCall['arguments'];
print('收到函数调用: $name, 参数: $arguments');
// 假设处理了函数调用并获得结果
final result = '{"temperature": 25, "condition": "sunny"}';
// 使用Stream API发送函数调用结果
final resultStream = openAIService.streamFunctionResult(
messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
functionCall: functionCall,
functionResult: result,
);
// 处理结果流
resultStream.listen(
(token) {
responseBuilder.write(token);
print(token); // 实时输出
},
onDone: () {
print('完整回复: ${responseBuilder.toString()}');
},
onError: (error) {
print('错误: $error');
}
);
}
);
// 启动请求
await openAIService.sendMessageStream(
messages: [userMessage],
systemPrompt: '你是一个有用的AI助手',
);
```
## 注意事项
1. 确保在使用前已正确初始化服务
2. 对于流式请求,确保在不需要时取消订阅
3. 处理函数调用时,确保提供有效的结果格式
4. 网络请求可能会失败,请确保加入适当的错误处理
## 许可证
[MIT License](LICENSE)

83
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt

@ -57,9 +57,6 @@ class SystemFunctionHandler(private val context: Context) {
// 注册拨打电话函数 // 注册拨打电话函数
registerMakePhoneCallFunction(client) registerMakePhoneCallFunction(client)
// 注册打开应用函数
registerOpenAppFunction(client)
// 注册设置提醒函数 // 注册设置提醒函数
registerSetReminderFunction(client) registerSetReminderFunction(client)
@ -200,35 +197,7 @@ class SystemFunctionHandler(private val context: Context) {
Log.d(TAG, "已注册拨打电话功能") Log.d(TAG, "已注册拨打电话功能")
} }
/**
* 注册打开应用函数
*/
private fun registerOpenAppFunction(client: MCPClient) {
client.registerLocalFunction(
"open_app",
"当用户请求打开特定应用时,启动相应应用",
JSONObject("""
{
"type": "object",
"properties": {
"app_name": {
"type": "string",
"description": "应用名称"
}
},
"required": ["app_name"]
}
"""),
object : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String {
val appName = arguments["app_name"] as? String ?: ""
val result = openApp(appName)
return "{\"result\": \"${result.message}\"}"
}
}
)
Log.d(TAG, "已注册打开应用功能")
}
/** /**
* 注册设置提醒函数 * 注册设置提醒函数
@ -764,55 +733,7 @@ class SystemFunctionHandler(private val context: Context) {
} }
} }
/**
* 打开应用程序
* @param appName 应用名称
* @return 操作结果
*/
fun openApp(appName: String): Result {
Log.d(TAG, "打开应用: $appName")
try {
// 查找应用的包名
val packageManager = context.packageManager
val intent = Intent(Intent.ACTION_MAIN, null)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
val apps = packageManager.queryIntentActivities(intent, 0)
for (resolveInfo in apps) {
val appLabel = resolveInfo.loadLabel(packageManager).toString()
if (appLabel.equals(appName, ignoreCase = true) ||
appLabel.contains(appName, ignoreCase = true)) {
// 找到匹配的应用
val packageName = resolveInfo.activityInfo.packageName
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
if (launchIntent != null) {
launchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(launchIntent)
return Result(
success = true,
message = "正在打开${appName}应用"
)
}
}
}
return Result(
success = false,
message = "未找到应用: $appName"
)
} catch (e: Exception) {
Log.e(TAG, "打开应用失败: ${e.message}", e)
return Result(
success = false,
message = "打开应用失败: ${e.message}"
)
}
}
/** /**
* 根据联系人名称获取电话号码 * 根据联系人名称获取电话号码

477
local_plugins/open_ai_service/ios/Classes/MCPClient.swift

@ -1,249 +1,366 @@
import Foundation import Foundation
/// 工具类型枚举 /// 工具类型枚举
enum ToolType { public enum ToolType {
case localFunction // 本地函数 case localFunction // 本地函数
case mcpTool // MCP工具 case mcpTool // MCP工具
} }
/// 函数处理器协议 /// 函数处理器协议
protocol FunctionHandler { public protocol FunctionHandler {
/// 处理函数调用 /// 处理函数调用
/// - Parameters: /// - Parameter arguments: 函数参数,字典格式
/// - arguments: 函数参数,Dictionary格式
/// - Returns: 函数执行结果,字符串 /// - Returns: 函数执行结果,字符串
func handle(arguments: [String: Any]) async throws -> String func handle(arguments: [String: Any]) async throws -> String
} }
/// MCPClient - iOS版本,负责连接到Model Context Protocol服务器和管理本地函数 /// 单个MCP子客户端
public class MCPClient { /// 负责连接到一个具体的MCP服务器并管理其工具
private let TAG = "MCPClient" public class MCPSubClient {
private let tag: String
private let serverId: String
private let serverUrl: String
// 本地函数Map,函数名 -> 处理器 // 服务器提供的工具列表
private var localFunctions = [String: FunctionHandler]() private var tools: [[String: Any]] = []
// 工具定义Map // 工具Map列表,用于OpenAI请求
private var toolDefinitions = [[String: Any]]() private var toolMaps: [[String: Any]] = []
// 是否已连接 // 是否已连接
private var isConnected = false private var isConnectedFlag = false
// 系统功能处理器 // 工具名称到工具的映射,用于快速查找
private var systemFunctionHandler: SystemFunctionHandler? private var toolMapping: [String: [String: Any]] = [:]
// SSE会话 // URL会话
private var sseSession: URLSession? private lazy var urlSession: URLSession = {
private var sseTask: URLSessionDataTask? let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 30
return URLSession(configuration: config)
}()
/// 初始化MCPClient public init(serverId: String, serverUrl: String) {
/// - Parameter context: 可选的应用上下文 self.serverId = serverId
init(_ context: Any? = nil) { self.serverUrl = serverUrl
if let appContext = context { self.tag = "MCPSubClient[\(serverId)]"
systemFunctionHandler = SystemFunctionHandler(context: appContext) print("\(tag) 初始化,URL: \(serverUrl)")
// 初始化语音功能
initializeVoiceFunctions()
}
} }
/// 初始化语音功能处理器 /// 连接到MCP服务器(通过SSE)
private func initializeVoiceFunctions() { /// - Returns: 是否连接成功
public func connect() async -> Bool {
do { do {
// 使用SystemFunctionHandler注册所有功能 print("\(tag) 开始连接服务器: \(serverUrl)")
systemFunctionHandler?.registerAllFunctions(client: self)
// 这里应该实现实际的SSE连接逻辑
// 由于iOS没有直接的SSE库,我们可以使用URLSession来模拟
// 或者使用第三方库如EventSource
print("\(TAG) 语音功能初始化完成") // 模拟连接成功并获取工具列表
await processToolsList()
isConnectedFlag = true
print("\(tag) 连接成功,可用工具: \(tools.count)个")
return true
} catch { } catch {
print("\(TAG) 语音功能初始化失败: \(error.localizedDescription)") print("\(tag) 连接失败: \(error.localizedDescription)")
return false
} }
} }
/// 注册本地函数 /// 处理工具列表
/// - Parameters: private func processToolsList() async {
/// - name: 函数名称 // 这里应该从MCP服务器获取实际的工具列表
/// - description: 函数描述 // 目前先模拟一些基本工具
/// - parameters: 函数参数定义 tools = []
/// - handler: 函数处理器
/// - Returns: 是否注册成功 // 更新工具映射
func registerLocalFunction(name: String, description: String, parameters: [String: Any], handler: FunctionHandler) -> Bool { toolMapping.removeAll()
do { for tool in tools {
// 存储函数处理器 if let name = tool["name"] as? String {
localFunctions[name] = handler toolMapping[name] = tool
}
}
// 更新工具Map列表
updateToolMaps()
print("\(tag) 成功处理 \(tools.count) 个工具")
}
/// 更新工具Map列表,用于OpenAI请求
/// 将MCP工具转换为OpenAI函数调用格式
private func updateToolMaps() {
toolMaps.removeAll()
for tool in tools {
guard let name = tool["name"] as? String,
let description = tool["description"] as? String else {
continue
}
// 创建函数定义 let functionMap: [String: Any] = [
let functionDef: [String: Any] = [
"name": name, "name": name,
"description": description, "description": description,
"parameters": parameters "parameters": [
"type": "object",
"properties": [:],
"required": []
]
] ]
// 添加到工具定义列表 toolMaps.append([
let toolDef: [String: Any] = [
"type": "function", "type": "function",
"function": functionDef "function": functionMap
] ])
// 更新或添加工具定义
if let index = toolDefinitions.firstIndex(where: {
guard let function = $0["function"] as? [String: Any],
let functionName = function["name"] as? String else {
return false
}
return functionName == name
}) {
toolDefinitions[index] = toolDef
} else {
toolDefinitions.append(toolDef)
}
print("\(TAG) 成功注册本地函数: \(name)")
return true
} catch {
print("\(TAG) 注册函数失败: \(error.localizedDescription)")
return false
} }
print("\(tag) 更新工具映射完成,共 \(toolMaps.count) 个工具")
} }
/// 连接到MCP服务器(通过SSE) /// 获取工具Map列表,用于OpenAI请求
/// - Parameter mcpServer: 服务器URL public func getToolMaps() -> [[String: Any]] {
/// - Returns: 是否连接成功 return toolMaps
func connectToSSE(_ mcpServer: String) -> Bool { }
// 关闭现有连接
closeConnection() /// 获取可用的工具列表
public func getTools() -> [[String: Any]] {
// 如果URL为空,返回失败 return tools
if mcpServer.isEmpty { }
print("\(TAG) MCP服务器URL为空")
return false /// 检查是否包含指定工具
public func containsTool(name: String) -> Bool {
return toolMapping.keys.contains(name)
}
/// 调用工具
public func callTool(name: String, arguments: [String: Any]) async throws -> [String: Any] {
guard isConnectedFlag else {
print("\(tag) 未连接到服务器")
return ["context": "未连接到MCP服务器"]
} }
// 解析服务器URL guard containsTool(name: name) else {
guard let serverURL = URL(string: mcpServer) else { print("\(tag) 此客户端不包含工具: \(name)")
print("\(TAG) 无效的MCP服务器URL: \(mcpServer)") throw OpenAIException("工具不存在: \(name)")
return false
} }
print("\(TAG) 正在连接到MCP服务器: \(mcpServer)") // 这里应该实现实际的工具调用逻辑
// 目前先返回模拟结果
return [
"name": name,
"context": "工具调用结果: \(name)"
]
}
/// 检查是否已连接
public func isConnected() -> Bool {
return isConnectedFlag
}
/// 关闭连接
public func close() {
isConnectedFlag = false
print("\(tag) 已关闭MCP服务器连接")
}
}
/// MCP客户端,用于连接Model Context Protocol服务器和管理本地函数
public class MCPClient {
private let tag = "MCPClient"
// 本地函数Map,函数名 -> 处理器
private var localFunctions: [String: FunctionHandler] = [:]
// 本地函数定义Map,函数名 -> 定义
private var localFunctionDefs: [String: String] = [:]
// 子客户端列表,每个连接一个MCP服务器
private var subClients: [String: MCPSubClient] = [:]
// 是否已连接
private var isConnectedFlag = false
// 系统功能处理器
private var systemFunctionHandler: SystemFunctionHandler?
public init() {
// 初始化系统功能处理器
systemFunctionHandler = SystemFunctionHandler()
initializeVoiceFunctions()
}
/// 初始化语音功能处理器
private func initializeVoiceFunctions() {
do {
// 使用SystemFunctionHandler注册所有功能
systemFunctionHandler?.registerAllFunctions(client: self)
print("\(tag) 语音功能初始化完成")
} catch {
print("\(tag) 语音功能初始化失败: \(error.localizedDescription)")
}
}
/// 连接到MCP服务器(通过SSE)
/// 直接接收完整的JSON配置字符串
/// - Parameter mcpConfigJson: 包含mcpServers字段的JSON配置字符串
/// - Returns: 是否连接成功
public func connectToSSE(mcpConfigJson: String) async throws -> Bool {
// 清除现有连接
closeAllConnections()
// 创建会话配置 var serverMap: [String: String] = [:]
let config = URLSessionConfiguration.default var configLoaded = false
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60
// 创建会话 // 尝试解析传入的JSON配置
let session = URLSession(configuration: config) if !mcpConfigJson.isEmpty {
self.sseSession = session do {
guard let data = mcpConfigJson.data(using: .utf8),
let config = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let mcpServers = config["mcpServers"] as? [String: [String: String]] else {
throw OpenAIException("无效的MCP配置格式")
}
// 从服务器配置中提取URL
for (serverId, serverConfig) in mcpServers {
if let url = serverConfig["url"], !url.isEmpty {
serverMap[serverId] = url
print("\(tag) 从配置中添加服务器 [\(serverId)]: \(url)")
}
}
configLoaded = !serverMap.isEmpty
} catch {
print("\(tag) 解析MCP配置失败: \(error.localizedDescription)")
}
}
// 创建请求 // 如果没有从配置中加载到服务器,使用默认配置
var request = URLRequest(url: serverURL) if !configLoaded {
request.httpMethod = "GET" print("\(tag) 使用默认MCP服务器配置")
request.addValue("text/event-stream", forHTTPHeaderField: "Accept") // 可以在这里添加默认的服务器配置
}
// 创建并启动SSE任务 // 连接到所有服务器
let task = session.dataTask(with: request) { [weak self] data, response, error in var successCount = 0
guard let self = self else { return } for (serverId, serverUrl) in serverMap {
let subClient = MCPSubClient(serverId: serverId, serverUrl: serverUrl)
let connected = await subClient.connect()
if let error = error { if connected {
print("\(self.TAG) MCP服务器连接失败: \(error.localizedDescription)") subClients[serverId] = subClient
self.isConnected = false successCount += 1
return print("\(tag) 成功连接到服务器 [\(serverId)]")
} } else {
print("\(tag) 连接服务器失败 [\(serverId)]")
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
print("\(self.TAG) 成功连接到MCP服务器")
self.isConnected = true
// 处理服务器响应数据
if let data = data, let responseString = String(data: data, encoding: .utf8) {
print("\(self.TAG) 收到初始响应: \(responseString)")
// 在这里解析可用工具列表,暂时模拟
// 实际情况下,需要实现SSE流解析
}
} else {
print("\(self.TAG) MCP服务器连接失败,状态码: \(httpResponse.statusCode)")
self.isConnected = false
}
} }
} }
task.resume() isConnectedFlag = successCount > 0
self.sseTask = task print("\(tag) MCP客户端连接完成,成功连接 \(successCount)/\(serverMap.count) 个服务器")
// 在iOS中,连接是异步的,此处返回true表示连接已开始 return isConnectedFlag
// 实际连接状态通过isConnected属性检查
return true
} }
/// 检查是否已连接 /// 关闭所有连接
/// - Returns: 是否已连接 private func closeAllConnections() {
func checkIsConnected() -> Bool { for (_, subClient) in subClients {
return isConnected subClient.close()
} }
subClients.removeAll()
/// 关闭连接 isConnectedFlag = false
func closeConnection() {
sseTask?.cancel()
sseTask = nil
isConnected = false
} }
/// 获取工具定义列表,用于OpenAI请求 /// 获取工具Map列表,用于OpenAI请求
/// - Returns: 工具定义列表 public func getToolMaps() -> [[String: Any]] {
func getToolMaps() -> [[String: Any]] { var allToolMaps: [[String: Any]] = []
return toolDefinitions
// 添加所有子客户端的工具
for (_, subClient) in subClients {
allToolMaps.append(contentsOf: subClient.getToolMaps())
}
// 添加本地函数
for (name, _) in localFunctions {
if let def = localFunctionDefs[name],
let data = def.data(using: .utf8),
let functionDef = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
allToolMaps.append([
"type": "function",
"function": functionDef
])
}
}
return allToolMaps
} }
/// 解析JSON参数 /// 注册本地函数
/// - Parameter argumentsJson: JSON字符串 public func registerLocalFunction(name: String, description: String, parameters: [String: Any], handler: FunctionHandler) -> Bool {
/// - Returns: 解析后的参数字典 do {
func parseJsonArguments(_ argumentsJson: String) -> [String: Any] { localFunctions[name] = handler
guard !argumentsJson.isEmpty,
let data = argumentsJson.data(using: .utf8), let functionDef: [String: Any] = [
let parsedArgs = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { "name": name,
return [:] "description": description,
"parameters": parameters
]
let jsonData = try JSONSerialization.data(withJSONObject: functionDef)
let jsonString = String(data: jsonData, encoding: .utf8) ?? ""
localFunctionDefs[name] = jsonString
print("\(tag) 成功注册本地函数: \(name)")
return true
} catch {
print("\(tag) 注册本地函数失败: \(error.localizedDescription)")
return false
} }
return parsedArgs
} }
/// 调用工具 /// 调用工具
/// - Parameters: public func callTool(name: String, arguments: [String: Any]) async throws -> [String: Any] {
/// - name: 工具名称 // 首先检查是否是本地函数
/// - arguments: 工具参数 if let handler = localFunctions[name] {
/// - Returns: 工具调用结果 do {
func callTool(name: String, arguments: [String: Any]) async -> [String: Any] {
do {
// 检查是否为本地函数
if let handler = localFunctions[name] {
let result = try await handler.handle(arguments: arguments) let result = try await handler.handle(arguments: arguments)
return [ return ["context": result]
"name": name, } catch {
"context": result throw OpenAIException("本地函数调用失败: \(error.localizedDescription)")
]
} }
}
// 如果不是本地函数,尝试作为MCP工具调用
if isConnected { // 然后检查MCP工具
// 这里应该实现真正的MCP工具调用 for (_, subClient) in subClients {
// 暂时返回一个模拟结果 if subClient.containsTool(name: name) {
return [ return try await subClient.callTool(name: name, arguments: arguments)
"name": name,
"context": "模拟MCP工具调用结果: \(name)(\(arguments))"
]
} }
return ["context": "无法处理工具调用:找不到工具 \(name) 或MCP未连接"]
} catch {
print("\(TAG) 调用工具失败: \(error.localizedDescription)")
return ["context": "工具调用失败: \(error.localizedDescription)"]
} }
throw OpenAIException("未找到工具: \(name)")
}
/// 解析JSON参数
public func parseJsonArguments(_ argumentsJson: String) throws -> [String: Any] {
guard let data = argumentsJson.data(using: .utf8),
let arguments = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return [:]
}
return arguments
} }
/// 关闭MCP客户端 /// 检查是否已连接
func close() { public func isConnected() -> Bool {
closeConnection() return isConnectedFlag || !localFunctions.isEmpty
}
/// 关闭连接
public func close() {
closeAllConnections()
localFunctions.removeAll() localFunctions.removeAll()
toolDefinitions.removeAll() localFunctionDefs.removeAll()
print("\(tag) 已关闭MCP客户端")
} }
} }

1315
local_plugins/open_ai_service/ios/Classes/OpenAIService.swift

File diff suppressed because it is too large

594
local_plugins/open_ai_service/ios/Classes/OpenAIServicePlugin.swift

@ -1,345 +1,86 @@
import Flutter import Flutter
import UIKit import UIKit
import Foundation
// 修改OpenAIService类,添加转发方法 /// OpenAI服务插件主类
extension OpenAIService {
// 为插件提供的公共发送消息方法
public func publicSendMessageStream(messages: [[String: Any]], systemPrompt: String, onToken: @escaping (String) -> Void, onComplete: @escaping () -> Void, onError: @escaping (Error) -> Void, onFunctionCall: @escaping ([String: Any]) -> Void, onFunctionCallResult: @escaping ([String: Any], [String: Any]) -> Void) {
let callback = StreamCallback(
onToken: onToken,
onComplete: onComplete,
onError: onError,
onFunctionCall: onFunctionCall,
onFunctionCallResult: onFunctionCallResult
)
sendMessageStream(messages: messages, systemPrompt: systemPrompt, callback: callback)
}
// 为插件提供的公共发送函数调用结果方法
public func publicSendFunctionCallResult(messages: [[String: Any]], systemPrompt: String, functionCall: [String: Any], functionResult: String, onToken: @escaping (String) -> Void, onComplete: @escaping () -> Void, onError: @escaping (Error) -> Void, onFunctionCall: @escaping ([String: Any]) -> Void, onFunctionCallResult: @escaping ([String: Any], [String: Any]) -> Void) {
let callback = StreamCallback(
onToken: onToken,
onComplete: onComplete,
onError: onError,
onFunctionCall: onFunctionCall,
onFunctionCallResult: onFunctionCallResult
)
sendFunctionCallResult(messages: messages, systemPrompt: systemPrompt, functionCall: functionCall, functionResult: functionResult, callback: callback)
}
}
public class OpenAIServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public class OpenAIServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
// OpenAI服务实例
private let openAIService = OpenAIService()
// 事件接收器 // MARK: - 常量
private static let methodChannelName = "com.yunqiinnovation.open_ai_service/methods"
private static let eventChannelName = "com.yunqiinnovation.open_ai_service/events"
// MARK: - 属性
private var methodChannel: FlutterMethodChannel?
private var eventChannel: FlutterEventChannel?
private var eventSink: FlutterEventSink? private var eventSink: FlutterEventSink?
private var openAIService: OpenAIService?
// 注册插件 // MARK: - 插件注册
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let methodChannel = FlutterMethodChannel(name: "com.yunqiinnovation.open_ai_service/methods", binaryMessenger: registrar.messenger())
let eventChannel = FlutterEventChannel(name: "com.yunqiinnovation.open_ai_service/events", binaryMessenger: registrar.messenger())
let instance = OpenAIServicePlugin() let instance = OpenAIServicePlugin()
// 初始化方法通道
let methodChannel = FlutterMethodChannel(
name: methodChannelName,
binaryMessenger: registrar.messenger()
)
instance.methodChannel = methodChannel
registrar.addMethodCallDelegate(instance, channel: methodChannel) registrar.addMethodCallDelegate(instance, channel: methodChannel)
// 初始化事件通道
let eventChannel = FlutterEventChannel(
name: eventChannelName,
binaryMessenger: registrar.messenger()
)
instance.eventChannel = eventChannel
eventChannel.setStreamHandler(instance) eventChannel.setStreamHandler(instance)
// 初始化OpenAI服务
instance.openAIService = OpenAIService()
} }
// 处理方法调用 // MARK: - FlutterPlugin
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let service = openAIService else {
result(FlutterError(code: "SERVICE_NOT_INITIALIZED",
message: "OpenAI服务未初始化",
details: nil))
return
}
switch call.method { switch call.method {
case "initialize": case "initialize":
if let args = call.arguments as? [String: Any], handleInitialize(call: call, result: result, service: service)
let apiKey = args["apiKey"] as? String {
let baseUrl = args["baseUrl"] as? String ?? ""
let model = args["model"] as? String ?? ""
let mcpServer = args["mcpServer"] as? String ?? ""
let initialized = openAIService.initialize(apiKey: apiKey, baseUrl: baseUrl, model: model, mcpServer: mcpServer)
result(initialized)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "初始化参数无效", details: nil))
}
case "registerFunction": case "registerFunction":
if let args = call.arguments as? [String: Any], handleRegisterFunction(call: call, result: result, service: service)
let name = args["name"] as? String,
let description = args["description"] as? String,
let parameters = args["parameters"] as? [String: Any] {
let registered = openAIService.registerFunction(name: name, description: description, parameters: parameters)
result(registered)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "函数注册参数无效", details: nil))
}
case "sendMessage": case "sendMessage":
guard let args = call.arguments as? [String: Any], handleSendMessage(call: call, result: result, service: service)
let messagesRaw = args["messages"] as? [[String: Any]] else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "发送消息参数无效", details: nil))
return
}
let systemPrompt = args["systemPrompt"] as? String ?? ""
// 在后台线程执行
DispatchQueue.global(qos: .userInitiated).async {
do {
let response = try self.openAIService.sendMessage(messages: messagesRaw, systemPrompt: systemPrompt)
// 在主线程返回结果
DispatchQueue.main.async {
result(response)
}
} catch {
// 在主线程返回错误
DispatchQueue.main.async {
result(FlutterError(code: "OPENAI_ERROR", message: error.localizedDescription, details: nil))
}
}
}
case "sendMessageStream": case "sendMessageStream":
guard let args = call.arguments as? [String: Any], handleSendMessageStream(call: call, result: result, service: service)
let messagesRaw = args["messages"] as? [[String: Any]] else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "发送消息参数无效", details: nil))
return
}
let systemPrompt = args["systemPrompt"] as? String ?? ""
// 检查事件接收器
guard let eventSink = self.eventSink else {
result(FlutterError(code: "NO_EVENT_SINK", message: "没有可用的事件流接收器", details: nil))
return
}
// 在后台线程执行
DispatchQueue.global(qos: .userInitiated).async {
self.openAIService.publicSendMessageStream(
messages: messagesRaw,
systemPrompt: systemPrompt,
onToken: { token in
DispatchQueue.main.async {
eventSink(["type": "token", "content": token])
}
},
onComplete: {
DispatchQueue.main.async {
eventSink(["type": "complete"])
}
},
onError: { error in
DispatchQueue.main.async {
eventSink(["type": "error", "content": error.localizedDescription])
}
},
onFunctionCall: { functionCall in
DispatchQueue.main.async {
do {
let data = try JSONSerialization.data(withJSONObject: functionCall)
if let jsonString = String(data: data, encoding: .utf8) {
eventSink(["type": "function_call", "content": jsonString])
} else {
eventSink(["type": "error", "content": "函数调用数据序列化失败"])
}
} catch {
eventSink(["type": "error", "content": "函数调用数据序列化失败: \(error.localizedDescription)"])
}
}
},
onFunctionCallResult: { functionCall, functionCallResult in
DispatchQueue.main.async {
do {
let functionCallData = try JSONSerialization.data(withJSONObject: functionCall)
let resultData = try JSONSerialization.data(withJSONObject: functionCallResult)
if let functionCallString = String(data: functionCallData, encoding: .utf8),
let resultString = String(data: resultData, encoding: .utf8) {
eventSink(["type": "function_call_result", "function_call": functionCallString, "result": resultString])
} else {
eventSink(["type": "error", "content": "函数调用结果数据序列化失败"])
}
} catch {
eventSink(["type": "error", "content": "函数调用结果数据序列化失败: \(error.localizedDescription)"])
}
}
}
)
// 请求已开始
DispatchQueue.main.async {
result(true)
}
}
case "sendFunctionCallResult": case "sendFunctionCallResult":
guard let args = call.arguments as? [String: Any], handleSendFunctionCallResult(call: call, result: result, service: service)
let messagesRaw = args["messages"] as? [[String: Any]],
let functionCallRaw = args["functionCall"] as? [String: Any],
let functionResult = args["functionResult"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "发送函数调用结果参数无效", details: nil))
return
}
let systemPrompt = args["systemPrompt"] as? String ?? ""
// 检查事件接收器
guard let eventSink = self.eventSink else {
result(FlutterError(code: "NO_EVENT_SINK", message: "没有可用的事件流接收器", details: nil))
return
}
// 在后台线程执行
DispatchQueue.global(qos: .userInitiated).async {
self.openAIService.publicSendFunctionCallResult(
messages: messagesRaw,
systemPrompt: systemPrompt,
functionCall: functionCallRaw,
functionResult: functionResult,
onToken: { token in
DispatchQueue.main.async {
eventSink(["type": "token", "content": token])
}
},
onComplete: {
DispatchQueue.main.async {
eventSink(["type": "complete"])
}
},
onError: { error in
DispatchQueue.main.async {
eventSink(["type": "error", "content": error.localizedDescription])
}
},
onFunctionCall: { functionCall in
DispatchQueue.main.async {
do {
let data = try JSONSerialization.data(withJSONObject: functionCall)
if let jsonString = String(data: data, encoding: .utf8) {
eventSink(["type": "function_call", "content": jsonString])
} else {
eventSink(["type": "error", "content": "函数调用数据序列化失败"])
}
} catch {
eventSink(["type": "error", "content": "函数调用数据序列化失败: \(error.localizedDescription)"])
}
}
},
onFunctionCallResult: { functionCall, functionCallResult in
DispatchQueue.main.async {
do {
let functionCallData = try JSONSerialization.data(withJSONObject: functionCall)
let resultData = try JSONSerialization.data(withJSONObject: functionCallResult)
if let functionCallString = String(data: functionCallData, encoding: .utf8),
let resultString = String(data: resultData, encoding: .utf8) {
eventSink(["type": "function_call_result", "function_call": functionCallString, "result": resultString])
} else {
eventSink(["type": "error", "content": "函数调用结果数据序列化失败"])
}
} catch {
eventSink(["type": "error", "content": "函数调用结果数据序列化失败: \(error.localizedDescription)"])
}
}
}
)
// 请求已开始
DispatchQueue.main.async {
result(true)
}
}
case "createUserMessage": case "createUserMessage":
if let args = call.arguments as? [String: Any], handleCreateUserMessage(call: call, result: result, service: service)
let content = args["content"] as? String {
let message = openAIService.createUserMessage(content: content)
result(message)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "创建用户消息参数无效", details: nil))
}
case "createAssistantMessage": case "createAssistantMessage":
if let args = call.arguments as? [String: Any], handleCreateAssistantMessage(call: call, result: result, service: service)
let content = args["content"] as? String {
let message = openAIService.createAssistantMessage(content: content)
result(message)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "创建助手消息参数无效", details: nil))
}
case "createUserMessageWithImage":
if let args = call.arguments as? [String: Any],
let text = args["text"] as? String,
let imageBase64 = args["imageBase64"] as? String {
let message = openAIService.createUserMessageWithImage(text: text, imageBase64: imageBase64)
result(message)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "创建图片消息参数无效", details: nil))
}
case "fileToBase64":
if let args = call.arguments as? [String: Any],
let filePath = args["filePath"] as? String {
let maxSizeKB = args["maxSizeKB"] as? Int ?? 20480
let base64 = openAIService.fileToBase64(filePath, maxSizeKB: maxSizeKB)
if let base64 = base64 {
result(base64)
} else {
result(FlutterError(code: "FILE_ERROR", message: "文件转换失败", details: nil))
}
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "文件转换参数无效", details: nil))
}
case "initializeMcpClient": case "initializeMcpClient":
if let args = call.arguments as? [String: Any], handleInitializeMcpClient(call: call, result: result, service: service)
let serverUrl = args["serverUrl"] as? String {
let success = openAIService.initializeMcpClient(serverUrl)
result(success)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "初始化MCP客户端参数无效", details: nil))
}
case "isMcpInitialized": case "isMcpInitialized":
result(openAIService.checkMcpInitialized()) handleIsMcpInitialized(call: call, result: result, service: service)
case "closeMcpClient": case "closeMcpClient":
result(openAIService.closeMcpClient()) handleCloseMcpClient(call: call, result: result, service: service)
case "handleMcpToolCall": case "handleMcpToolCall":
if let args = call.arguments as? [String: Any], handleMcpToolCall(call: call, result: result, service: service)
let functionCallJson = args["functionCall"] as? String {
// 解析函数调用JSON
guard let data = functionCallJson.data(using: .utf8),
let functionCall = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "无效的函数调用JSON", details: nil))
return
}
// 在后台线程执行
Task {
do {
let toolResult = await openAIService.handleMcpToolCall(functionCall)
// 在主线程返回结果
DispatchQueue.main.async {
result(toolResult)
}
} catch {
// 在主线程返回错误
DispatchQueue.main.async {
result(FlutterError(code: "MCP_TOOL_ERROR", message: "处理MCP工具调用失败: \(error.localizedDescription)", details: nil))
}
}
}
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "处理MCP工具调用参数无效", details: nil))
}
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
@ -347,7 +88,6 @@ public class OpenAIServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler
} }
// MARK: - FlutterStreamHandler // MARK: - FlutterStreamHandler
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events self.eventSink = events
return nil return nil
@ -357,4 +97,244 @@ public class OpenAIServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler
self.eventSink = nil self.eventSink = nil
return nil return nil
} }
// MARK: - 方法处理
private func handleInitialize(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let apiKey = args["apiKey"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少必要参数",
details: nil))
return
}
let baseUrl = args["baseUrl"] as? String ?? ""
let model = args["model"] as? String ?? ""
let mcpServer = args["mcpServer"] as? String ?? ""
let success = service.initialize(
apiKey: apiKey,
baseUrl: baseUrl,
model: model,
mcpServer: mcpServer
)
result(success)
}
private func handleRegisterFunction(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let name = args["name"] as? String,
let description = args["description"] as? String,
let parameters = args["parameters"] as? [String: Any] else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少必要参数",
details: nil))
return
}
let success = service.registerFunction(name: name, description: description, parameters: parameters)
result(success)
}
private func handleSendMessage(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let messagesArray = args["messages"] as? [[String: Any]] else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少消息参数",
details: nil))
return
}
do {
let response = try service.sendMessage(messages: messagesArray)
result(response)
} catch {
result(FlutterError(code: "OPENAI_ERROR",
message: error.localizedDescription,
details: nil))
}
}
private func handleSendMessageStream(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let messagesArray = args["messages"] as? [[String: Any]] else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少消息参数",
details: nil))
return
}
guard let eventSink = self.eventSink else {
result(FlutterError(code: "NO_EVENT_SINK",
message: "没有可用的事件流接收器",
details: nil))
return
}
// 创建回调处理器
let callback = StreamCallbackImpl(eventSink: eventSink)
// 发送流式消息
service.sendMessageStream(messages: messagesArray, callback: callback)
result(true)
}
private func handleSendFunctionCallResult(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let messagesArray = args["messages"] as? [[String: Any]],
let functionCall = args["functionCall"] as? [String: Any],
let functionResult = args["functionResult"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少必要参数",
details: nil))
return
}
guard let eventSink = self.eventSink else {
result(FlutterError(code: "NO_EVENT_SINK",
message: "没有可用的事件流接收器",
details: nil))
return
}
// 创建回调处理器
let callback = StreamCallbackImpl(eventSink: eventSink)
// 发送函数调用结果
service.sendFunctionCallResult(
messages: messagesArray,
functionCall: functionCall,
functionResult: functionResult,
callback: callback
)
result(true)
}
private func handleCreateUserMessage(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let content = args["content"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少内容参数",
details: nil))
return
}
let message = service.createUserMessage(content: content)
result(message)
}
private func handleCreateAssistantMessage(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let content = args["content"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少内容参数",
details: nil))
return
}
let message = service.createAssistantMessage(content: content)
result(message)
}
private func handleInitializeMcpClient(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let serverUrl = args["serverUrl"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少服务器URL参数",
details: nil))
return
}
let success = service.initializeMcpClient(serverUrl: serverUrl)
result(success)
}
private func handleIsMcpInitialized(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
let isInitialized = service.isMcpInitialized()
result(isInitialized)
}
private func handleCloseMcpClient(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
service.closeMcpClient()
result(true)
}
private func handleMcpToolCall(call: FlutterMethodCall, result: @escaping FlutterResult, service: OpenAIService) {
guard let args = call.arguments as? [String: Any],
let functionCallJson = args["functionCall"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS",
message: "缺少函数调用参数",
details: nil))
return
}
Task {
do {
let toolResult = try await service.handleMcpToolCall(functionCallJson: functionCallJson)
DispatchQueue.main.async {
result(toolResult)
}
} catch {
DispatchQueue.main.async {
result(FlutterError(code: "MCP_TOOL_ERROR",
message: "处理MCP工具调用失败: \(error.localizedDescription)",
details: nil))
}
}
}
}
}
// MARK: - 流式回调实现
private class StreamCallbackImpl: StreamCallback {
private let eventSink: FlutterEventSink
init(eventSink: @escaping FlutterEventSink) {
self.eventSink = eventSink
}
func onToken(_ token: String) {
DispatchQueue.main.async {
self.eventSink(["type": "token", "content": token])
}
}
func onComplete() {
DispatchQueue.main.async {
self.eventSink(["type": "complete"])
}
}
func onError(_ error: Error) {
DispatchQueue.main.async {
self.eventSink(["type": "error", "content": error.localizedDescription])
}
}
func onFunctionCall(_ functionCall: [String: Any]) {
DispatchQueue.main.async {
if let jsonData = try? JSONSerialization.data(withJSONObject: functionCall),
let jsonString = String(data: jsonData, encoding: .utf8) {
self.eventSink(["type": "function_call", "content": jsonString])
}
}
}
func onFunctionCallResult(_ functionCall: [String: Any], _ functionCallResult: [String: Any]) {
DispatchQueue.main.async {
if let callData = try? JSONSerialization.data(withJSONObject: functionCall),
let callString = String(data: callData, encoding: .utf8),
let resultData = try? JSONSerialization.data(withJSONObject: functionCallResult),
let resultString = String(data: resultData, encoding: .utf8) {
self.eventSink([
"type": "function_call_result",
"function_call": callString,
"result": resultString
])
}
}
}
} }

760
local_plugins/open_ai_service/ios/Classes/SystemFunctionHandler.swift

@ -1,98 +1,131 @@
import Foundation import Foundation
import UIKit import UIKit
import CoreLocation
import Contacts import Contacts
import EventKit import EventKit
import CoreLocation
import MediaPlayer
import AVFoundation
/// 系统功能处理器 /// 操作结果
class SystemFunctionHandler { public struct Result {
private let TAG = "SystemFunctionHandler" let success: Bool
private let context: Any let message: String
static let ACTION_EXIT_INTERACTION = "com.yunqiinnovation.deepsound.action.EXIT_INTERACTION"
public init(success: Bool, message: String) {
/// 初始化系统功能处理器 self.success = success
/// - Parameter context: 应用上下文 self.message = message
init(context: Any) {
self.context = context
}
/// 注册所有系统功能
/// - Parameter client: MCP客户端
func registerAllFunctions(client: MCPClient) {
// 注册退出交互函数
registerExitInteractionFunction(client)
// 注册翻译模式函数
registerTranslationModeFunction(client)
// 注册发送短信函数
registerSendTextMessageFunction(client)
// 注册拨打电话函数
registerMakePhoneCallFunction(client)
// 注册打开应用函数
registerOpenAppFunction(client)
// 注册设置提醒函数
registerSetReminderFunction(client)
// 注册获取当前时间函数
registerGetCurrentTimeFunction(client)
// 注册获取当前位置函数
registerGetCurrentLocationFunction(client)
print("\(TAG) 所有系统功能注册完成")
} }
}
/// 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能
public class SystemFunctionHandler: NSObject {
private let tag = "SystemFunctionHandler"
// 位置管理器
private var locationManager: CLLocationManager?
private var locationCompletion: ((Result) -> Void)?
// 事件存储
private var eventStore: EKEventStore?
public override init() {
super.init()
setupLocationManager()
setupEventStore()
}
/// 设置位置管理器
private func setupLocationManager() {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
}
/// 设置事件存储
private func setupEventStore() {
eventStore = EKEventStore()
}
/// 在MCPClient中注册所有系统功能
/// - Parameter client: MCPClient实例
public func registerAllFunctions(client: MCPClient) {
do {
// 注册退出交互函数
registerExitInteractionFunction(client: client)
// 注册翻译模式函数
registerTranslationModeFunction(client: client)
// 注册发送短信函数
registerSendTextMessageFunction(client: client)
// 注册拨打电话函数
registerMakePhoneCallFunction(client: client)
// 注册设置提醒函数
registerSetReminderFunction(client: client)
// 注册获取当前时间函数
registerGetCurrentTimeFunction(client: client)
// 注册获取当前位置函数
registerGetCurrentLocationFunction(client: client)
// 注册媒体控制函数
registerMediaPlayFunction(client: client)
registerMediaPauseFunction(client: client)
registerMediaPreviousFunction(client: client)
registerMediaNextFunction(client: client)
// 注册打开录音机功能
registerOpenRecorderFunction(client: client)
print("\(tag) 所有系统功能注册完成")
} catch {
print("\(tag) 注册系统功能失败: \(error.localizedDescription)")
}
}
// MARK: - 函数注册
/// 注册退出交互函数 /// 注册退出交互函数
private func registerExitInteractionFunction(_ client: MCPClient) { private func registerExitInteractionFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [:], "properties": [:],
"required": [] "required": []
] ]
// 创建函数处理器
let handler = ExitInteractionHandler() let handler = ExitInteractionHandler()
_ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "exit_interaction", name: "exit_interaction",
description: "结束当前交互", description: "结束当前交互",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册退出交互功能") print("\(tag) 已注册退出交互功能")
} }
/// 注册翻译模式函数 /// 注册翻译模式函数
private func registerTranslationModeFunction(_ client: MCPClient) { private func registerTranslationModeFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [:], "properties": [:],
"required": [] "required": []
] ]
// 创建函数处理器
let handler = TranslationModeHandler() let handler = TranslationModeHandler()
_ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "enter_translation_mode", name: "enter_translation_mode",
description: "用户请求进入实时翻译模式时,启动实时翻译功能", description: "用户请求进入实时翻译模式时,启动实时翻译功能",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册翻译模式功能") print("\(tag) 已注册翻译模式功能")
} }
/// 注册发送短信函数 /// 注册发送短信函数
private func registerSendTextMessageFunction(_ client: MCPClient) { private func registerSendTextMessageFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [ "properties": [
@ -108,22 +141,18 @@ class SystemFunctionHandler {
"required": ["contact", "message"] "required": ["contact", "message"]
] ]
// 创建函数处理器
let handler = SendTextMessageHandler() let handler = SendTextMessageHandler()
_ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "send_text_message", name: "send_text_message",
description: "发送短信", description: "发送短信",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册发送短信功能") print("\(tag) 已注册发送短信功能")
} }
/// 注册拨打电话函数 /// 注册拨打电话函数
private func registerMakePhoneCallFunction(_ client: MCPClient) { private func registerMakePhoneCallFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [ "properties": [
@ -135,49 +164,18 @@ class SystemFunctionHandler {
"required": ["contact"] "required": ["contact"]
] ]
// 创建函数处理器
let handler = MakePhoneCallHandler() let handler = MakePhoneCallHandler()
_ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "make_phone_call", name: "make_phone_call",
description: "拨打电话", description: "拨打电话",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册拨打电话功能") print("\(tag) 已注册拨打电话功能")
}
/// 注册打开应用函数
private func registerOpenAppFunction(_ client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [
"type": "object",
"properties": [
"app_name": [
"type": "string",
"description": "应用名称"
]
],
"required": ["app_name"]
]
// 创建函数处理器
let handler = OpenAppHandler()
// 注册函数
client.registerLocalFunction(
name: "open_app",
description: "当用户请求打开特定应用时,启动相应应用",
parameters: parameters,
handler: handler
)
print("\(TAG) 已注册打开应用功能")
} }
/// 注册设置提醒函数 /// 注册设置提醒函数
private func registerSetReminderFunction(_ client: MCPClient) { private func registerSetReminderFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [ "properties": [
@ -197,22 +195,18 @@ class SystemFunctionHandler {
"required": ["title", "time"] "required": ["title", "time"]
] ]
// 创建函数处理器 let handler = SetReminderHandler(eventStore: eventStore)
let handler = SetReminderHandler() _ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "set_reminder", name: "set_reminder",
description: "设置提醒事项", description: "设置提醒事项",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册设置提醒功能") print("\(tag) 已注册设置提醒功能")
} }
/// 注册获取当前时间函数 /// 注册获取当前时间函数
private func registerGetCurrentTimeFunction(_ client: MCPClient) { private func registerGetCurrentTimeFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [ "properties": [
@ -224,291 +218,469 @@ class SystemFunctionHandler {
"required": [] "required": []
] ]
// 创建函数处理器
let handler = GetCurrentTimeHandler() let handler = GetCurrentTimeHandler()
_ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "get_current_time", name: "get_current_time",
description: "获取当前日期和时间", description: "获取当前日期和时间",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册获取当前时间功能") print("\(tag) 已注册获取当前时间功能")
} }
/// 注册获取当前位置函数 /// 注册获取当前位置函数
private func registerGetCurrentLocationFunction(_ client: MCPClient) { private func registerGetCurrentLocationFunction(client: MCPClient) {
// 定义函数参数
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [:], "properties": [:],
"required": [] "required": []
] ]
// 创建函数处理器 let handler = GetCurrentLocationHandler(systemHandler: self)
let handler = GetCurrentLocationHandler() _ = client.registerLocalFunction(
// 注册函数
client.registerLocalFunction(
name: "get_current_location", name: "get_current_location",
description: "获取当前地理位置", description: "获取当前地理位置",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
print("\(TAG) 已注册获取当前位置功能") print("\(tag) 已注册获取当前位置功能")
}
/// 注册媒体播放功能
private func registerMediaPlayFunction(client: MCPClient) {
let parameters: [String: Any] = [
"type": "object",
"properties": [:],
"required": []
]
let handler = MediaPlayHandler()
_ = client.registerLocalFunction(
name: "media_play",
description: "播放媒体",
parameters: parameters,
handler: handler
)
print("\(tag) 已注册媒体播放功能")
}
/// 注册媒体暂停功能
private func registerMediaPauseFunction(client: MCPClient) {
let parameters: [String: Any] = [
"type": "object",
"properties": [:],
"required": []
]
let handler = MediaPauseHandler()
_ = client.registerLocalFunction(
name: "media_pause",
description: "暂停媒体播放",
parameters: parameters,
handler: handler
)
print("\(tag) 已注册媒体暂停功能")
}
/// 注册媒体上一首功能
private func registerMediaPreviousFunction(client: MCPClient) {
let parameters: [String: Any] = [
"type": "object",
"properties": [:],
"required": []
]
let handler = MediaPreviousHandler()
_ = client.registerLocalFunction(
name: "media_previous",
description: "播放上一首",
parameters: parameters,
handler: handler
)
print("\(tag) 已注册媒体上一首功能")
}
/// 注册媒体下一首功能
private func registerMediaNextFunction(client: MCPClient) {
let parameters: [String: Any] = [
"type": "object",
"properties": [:],
"required": []
]
let handler = MediaNextHandler()
_ = client.registerLocalFunction(
name: "media_next",
description: "播放下一首",
parameters: parameters,
handler: handler
)
print("\(tag) 已注册媒体下一首功能")
}
/// 注册打开录音机功能
private func registerOpenRecorderFunction(client: MCPClient) {
let parameters: [String: Any] = [
"type": "object",
"properties": [:],
"required": []
]
let handler = OpenRecorderHandler()
_ = client.registerLocalFunction(
name: "open_recorder",
description: "打开系统录音机并开始录音",
parameters: parameters,
handler: handler
)
print("\(tag) 已注册打开录音机功能")
}
// MARK: - 内部方法
/// 获取当前位置
internal func getCurrentLocation() async -> Result {
return await withCheckedContinuation { continuation in
locationCompletion = { result in
continuation.resume(returning: result)
}
guard let locationManager = locationManager else {
continuation.resume(returning: Result(success: false, message: "位置管理器未初始化"))
return
}
// 检查位置权限
let authStatus = locationManager.authorizationStatus
switch authStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
case .denied, .restricted:
continuation.resume(returning: Result(success: false, message: "位置权限被拒绝"))
return
case .authorizedWhenInUse, .authorizedAlways:
break
@unknown default:
continuation.resume(returning: Result(success: false, message: "未知的位置权限状态"))
return
}
// 检查位置服务是否可用
guard CLLocationManager.locationServicesEnabled() else {
continuation.resume(returning: Result(success: false, message: "位置服务未启用"))
return
}
// 请求位置
locationManager.requestLocation()
}
} }
} }
// MARK: - CLLocationManagerDelegate
extension SystemFunctionHandler: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
locationCompletion?(Result(success: false, message: "无法获取位置信息"))
return
}
let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude
let message = "当前位置:纬度 \(latitude),经度 \(longitude)"
locationCompletion?(Result(success: true, message: message))
locationCompletion = nil
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationCompletion?(Result(success: false, message: "获取位置失败:\(error.localizedDescription)"))
locationCompletion = nil
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
manager.requestLocation()
} else if status == .denied || status == .restricted {
locationCompletion?(Result(success: false, message: "位置权限被拒绝"))
locationCompletion = nil
}
}
}
// MARK: - 功能处理器实现
/// 退出交互处理器 /// 退出交互处理器
class ExitInteractionHandler: FunctionHandler { private class ExitInteractionHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
// 发送通知和广播 // 发送退出交互通知
NotificationCenter.default.post( NotificationCenter.default.post(name: NSNotification.Name("ExitInteraction"), object: nil)
name: Notification.Name(SystemFunctionHandler.ACTION_EXIT_INTERACTION),
object: nil,
userInfo: ["timestamp": Date().timeIntervalSince1970]
)
return "{\"result\": \"已结束当前交互\"}" return "{\"result\": \"已结束当前交互\"}"
} }
} }
/// 翻译模式处理器 /// 翻译模式处理器
class TranslationModeHandler: FunctionHandler { private class TranslationModeHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
// 发送通知 // 发送进入翻译模式通知
NotificationCenter.default.post( NotificationCenter.default.post(name: NSNotification.Name("EnterTranslationMode"), object: nil)
name: Notification.Name("com.yunqiinnovation.deepsound.ACTION_ENTER_TRANSLATION_MODE"), return "{\"result\": \"已进入翻译模式\"}"
object: nil,
userInfo: ["timestamp": Date().timeIntervalSince1970]
)
return "{\"result\": \"已进入实时翻译模式\"}"
} }
} }
/// 发送短信处理器 /// 发送短信处理器
class SendTextMessageHandler: FunctionHandler { private class SendTextMessageHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
guard let contact = arguments["contact"] as? String, guard let contact = arguments["contact"] as? String,
let message = arguments["message"] as? String else { let message = arguments["message"] as? String else {
return "{\"result\": \"发送短信失败:缺少联系人或消息内容\"}" throw OpenAIException("缺少必要参数")
} }
// iOS不允许应用直接发送短信,但可以打开短信应用 let result = sendTextMessage(contact: contact, message: message)
if let url = URL(string: "sms:\(contact)&body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") { return "{\"result\": \"\(result.message)\"}"
DispatchQueue.main.async { }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} private func sendTextMessage(contact: String, message: String) -> Result {
return "{\"result\": \"已打开短信应用,收件人:\(contact)\"}" // 在iOS中,我们只能打开短信应用,无法直接发送短信
guard let url = URL(string: "sms:\(contact)&body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else {
return Result(success: false, message: "无法创建短信URL")
} }
return "{\"result\": \"发送短信失败:无法打开短信应用\"}" if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return Result(success: true, message: "已打开短信应用")
} else {
return Result(success: false, message: "无法打开短信应用")
}
} }
} }
/// 拨打电话处理器 /// 拨打电话处理器
class MakePhoneCallHandler: FunctionHandler { private class MakePhoneCallHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
guard let contact = arguments["contact"] as? String else { guard let contact = arguments["contact"] as? String else {
return "{\"result\": \"拨打电话失败:缺少联系人\"}" throw OpenAIException("缺少联系人参数")
}
let phoneNumber = contact.replacingOccurrences(of: "[^+0-9]", with: "", options: .regularExpression)
if let url = URL(string: "tel://\(phoneNumber)") {
DispatchQueue.main.async {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
return "{\"result\": \"正在拨打\(contact)的电话\"}"
} }
return "{\"result\": \"拨打电话失败:无法拨打电话\"}" let result = makePhoneCall(contact: contact)
return "{\"result\": \"\(result.message)\"}"
} }
}
private func makePhoneCall(contact: String) -> Result {
/// 打开应用处理器 guard let url = URL(string: "tel:\(contact)") else {
class OpenAppHandler: FunctionHandler { return Result(success: false, message: "无法创建电话URL")
func handle(arguments: [String: Any]) async throws -> String {
guard let appName = arguments["app_name"] as? String else {
return "{\"result\": \"打开应用失败:缺少应用名称\"}"
} }
// iOS没有直接打开其他应用的API,只能通过URL Scheme if UIApplication.shared.canOpenURL(url) {
// 这里只是模拟返回成功,实际上无法在iOS上实现这个功能 UIApplication.shared.open(url)
return "{\"result\": \"打开应用功能在iOS上受限,无法直接打开\(appName)\"}" return Result(success: true, message: "已发起电话呼叫")
} else {
return Result(success: false, message: "无法拨打电话")
}
} }
} }
/// 设置提醒处理器 /// 设置提醒处理器
class SetReminderHandler: FunctionHandler { private class SetReminderHandler: FunctionHandler {
private let eventStore: EKEventStore?
init(eventStore: EKEventStore?) {
self.eventStore = eventStore
}
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
guard let title = arguments["title"] as? String, guard let title = arguments["title"] as? String,
let timeStr = arguments["time"] as? String else { let timeString = arguments["time"] as? String else {
return "{\"result\": \"设置提醒失败:缺少标题或时间\"}" throw OpenAIException("缺少必要参数")
} }
let content = arguments["content"] as? String ?? "" let content = arguments["content"] as? String ?? ""
let result = await setReminder(title: title, content: content, time: timeString)
return "{\"result\": \"\(result.message)\"}"
}
private func setReminder(title: String, content: String, time: String) async -> Result {
guard let eventStore = eventStore else {
return Result(success: false, message: "事件存储未初始化")
}
// 请求日历权限
let status = await requestCalendarAccess()
guard status == .authorized else {
return Result(success: false, message: "日历权限被拒绝")
}
// 解析时间 // 解析时间
let dateFormatter = DateFormatter() let formatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm" formatter.dateFormat = "yyyy-MM-dd HH:mm"
guard let date = dateFormatter.date(from: timeStr) else { guard let date = formatter.date(from: time) else {
return "{\"result\": \"设置提醒失败:时间格式无效,请使用'yyyy-MM-dd HH:mm'格式\"}" return Result(success: false, message: "时间格式错误")
} }
// 使用EventKit添加提醒 // 创建提醒
let eventStore = EKEventStore() let reminder = EKReminder(eventStore: eventStore)
let event = EKEvent(eventStore: eventStore) reminder.title = title
event.title = title reminder.notes = content
event.notes = content reminder.calendar = eventStore.defaultCalendarForNewReminders()
event.startDate = date
event.endDate = date.addingTimeInterval(3600) // 1小时后 let alarm = EKAlarm(absoluteDate: date)
event.calendar = eventStore.defaultCalendarForNewEvents reminder.addAlarm(alarm)
do { do {
try eventStore.save(event, span: .thisEvent) try eventStore.save(reminder, commit: true)
return "{\"result\": \"已创建提醒: \(title), 时间: \(timeStr)\"}" return Result(success: true, message: "提醒设置成功")
} catch { } catch {
return "{\"result\": \"设置提醒失败:\(error.localizedDescription)\"}" return Result(success: false, message: "设置提醒失败:\(error.localizedDescription)")
}
}
private func requestCalendarAccess() async -> EKAuthorizationStatus {
return await withCheckedContinuation { continuation in
eventStore?.requestAccess(to: .reminder) { granted, error in
let status = EKEventStore.authorizationStatus(for: .reminder)
continuation.resume(returning: status)
}
} }
} }
} }
/// 获取当前时间处理器 /// 获取当前时间处理器
class GetCurrentTimeHandler: FunctionHandler { private class GetCurrentTimeHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
let format = arguments["format"] as? String let format = arguments["format"] as? String
let result = getCurrentTime(format: format)
return "{\"result\": \"\(result.message)\", \"time\": \"\(result.message)\"}"
}
private func getCurrentTime(format: String?) -> Result {
let formatter = DateFormatter()
let calendar = Calendar.current if let format = format, !format.isEmpty {
let date = Date() formatter.dateFormat = format
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "zh_CN")
let timeStr: String
switch format?.lowercased() {
case "short":
dateFormatter.dateFormat = "HH:mm"
timeStr = dateFormatter.string(from: date)
case "date":
dateFormatter.dateFormat = "yyyy年MM月dd日"
timeStr = dateFormatter.string(from: date)
case "time":
dateFormatter.dateFormat = "HH:mm:ss"
timeStr = dateFormatter.string(from: date)
case "full":
dateFormatter.dateFormat = "yyyy年MM月dd日 EEEE HH:mm:ss"
timeStr = dateFormatter.string(from: date)
case "iso":
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
timeStr = dateFormatter.string(from: date)
default:
dateFormatter.dateFormat = "yyyy年MM月dd日 HH:mm"
timeStr = dateFormatter.string(from: date)
}
// 获取星期几
let weekDay: String
if format == "full" {
weekDay = "" // 已经包含在dateFormatter里
} else {
let weekDayNum = calendar.component(.weekday, from: date)
switch weekDayNum {
case 1: weekDay = "星期日"
case 2: weekDay = "星期一"
case 3: weekDay = "星期二"
case 4: weekDay = "星期三"
case 5: weekDay = "星期四"
case 6: weekDay = "星期五"
case 7: weekDay = "星期六"
default: weekDay = ""
}
}
// 格式化结果
let resultTime: String
if format == "short" || format == "time" {
resultTime = timeStr
} else if format == "full" {
resultTime = timeStr // already contains weekday
} else { } else {
resultTime = "\(timeStr) \(weekDay)" formatter.dateStyle = .full
formatter.timeStyle = .full
formatter.locale = Locale(identifier: "zh_CN")
} }
return "{\"result\": \"\(resultTime)\", \"time\": \"\(resultTime)\"}" let currentTime = formatter.string(from: Date())
return Result(success: true, message: currentTime)
} }
} }
/// 获取当前位置处理器 /// 获取当前位置处理器
class GetCurrentLocationHandler: NSObject, FunctionHandler { private class GetCurrentLocationHandler: FunctionHandler {
private let locationManager = CLLocationManager() private weak var systemHandler: SystemFunctionHandler?
private var locationCompletionHandler: ((String, Bool) -> Void)?
init(systemHandler: SystemFunctionHandler) {
self.systemHandler = systemHandler
}
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
return await withCheckedContinuation { continuation in guard let systemHandler = systemHandler else {
self.getLocation { message, success in throw OpenAIException("系统处理器未初始化")
continuation.resume(returning: "{\"result\": \"\(message)\", \"success\": \(success)}")
}
} }
let result = await systemHandler.getCurrentLocation()
return "{\"result\": \"\(result.message)\", \"success\": \(result.success)}"
}
}
/// 媒体播放处理器
private class MediaPlayHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String {
let result = controlMedia(action: "play")
return "{\"result\": \"\(result.message)\"}"
} }
private func getLocation(completion: @escaping (String, Bool) -> Void) { private func controlMedia(action: String) -> Result {
locationCompletionHandler = completion let commandCenter = MPRemoteCommandCenter.shared()
// 检查权限 switch action {
let authStatus = CLLocationManager.authorizationStatus() case "play":
if authStatus == .denied || authStatus == .restricted { // 发送播放命令通知
completion("需要位置权限才能获取当前位置", false) NotificationCenter.default.post(name: NSNotification.Name("MediaPlay"), object: nil)
return return Result(success: true, message: "已开始播放媒体")
default:
return Result(success: false, message: "未知的媒体操作")
} }
}
// 检查位置服务是否开启 }
if !CLLocationManager.locationServicesEnabled() {
completion("位置服务未启用,请打开GPS或网络定位", false) /// 媒体暂停处理器
return private class MediaPauseHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String {
let result = controlMedia(action: "pause")
return "{\"result\": \"\(result.message)\"}"
}
private func controlMedia(action: String) -> Result {
switch action {
case "pause":
// 发送暂停命令通知
NotificationCenter.default.post(name: NSNotification.Name("MediaPause"), object: nil)
return Result(success: true, message: "已暂停媒体播放")
default:
return Result(success: false, message: "未知的媒体操作")
} }
}
// 如果有最后的位置,使用它 }
if let lastLocation = locationManager.location {
let message = "当前位置: 经度 \(lastLocation.coordinate.longitude), 纬度 \(lastLocation.coordinate.latitude)" /// 媒体上一首处理器
completion(message, true) private class MediaPreviousHandler: FunctionHandler {
return func handle(arguments: [String: Any]) async throws -> String {
let result = controlMedia(action: "previous")
return "{\"result\": \"\(result.message)\"}"
}
private func controlMedia(action: String) -> Result {
switch action {
case "previous":
// 发送上一首命令通知
NotificationCenter.default.post(name: NSNotification.Name("MediaPrevious"), object: nil)
return Result(success: true, message: "已切换到上一首")
default:
return Result(success: false, message: "未知的媒体操作")
} }
}
// 否则请求更新 }
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest /// 媒体下一首处理器
locationManager.requestWhenInUseAuthorization() private class MediaNextHandler: FunctionHandler {
locationManager.startUpdatingLocation() func handle(arguments: [String: Any]) async throws -> String {
let result = controlMedia(action: "next")
// 设置超时 return "{\"result\": \"\(result.message)\"}"
DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in }
if let handler = self?.locationCompletionHandler {
self?.locationManager.stopUpdatingLocation() private func controlMedia(action: String) -> Result {
handler("获取位置超时,请稍后重试", false) switch action {
self?.locationCompletionHandler = nil case "next":
} // 发送下一首命令通知
NotificationCenter.default.post(name: NSNotification.Name("MediaNext"), object: nil)
return Result(success: true, message: "已切换到下一首")
default:
return Result(success: false, message: "未知的媒体操作")
} }
} }
} }
// 扩展以接收位置更新 /// 打开录音机处理器
extension GetCurrentLocationHandler: CLLocationManagerDelegate { private class OpenRecorderHandler: FunctionHandler {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func handle(arguments: [String: Any]) async throws -> String {
guard let location = locations.last, let handler = locationCompletionHandler else { return } let result = openRecorder()
return "{\"result\": \"\(result.message)\"}"
manager.stopUpdatingLocation()
let message = "当前位置: 经度 \(location.coordinate.longitude), 纬度 \(location.coordinate.latitude)"
handler(message, true)
locationCompletionHandler = nil
} }
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { private func openRecorder() -> Result {
manager.stopUpdatingLocation() // 尝试打开语音备忘录应用
locationCompletionHandler?("获取位置失败: \(error.localizedDescription)", false) if let url = URL(string: "voicememos://") {
locationCompletionHandler = nil if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return Result(success: true, message: "已打开语音备忘录")
}
}
return Result(success: false, message: "无法打开录音机应用")
} }
} }

22
local_plugins/open_ai_service/ios/open_ai_service.podspec

@ -1,13 +1,9 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint open_ai_service.podspec` to validate before publishing.
#
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'open_ai_service' s.name = 'open_ai_service'
s.version = '0.0.1' s.version = '0.0.1'
s.summary = 'OpenAI Service plugin' s.summary = 'OpenAI service plugin for iOS'
s.description = <<-DESC s.description = <<-DESC
A Flutter plugin for OpenAI services. A Flutter plugin that provides OpenAI service functionality for iOS.
DESC DESC
s.homepage = 'http://example.com' s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' } s.license = { :file => '../LICENSE' }
@ -15,9 +11,21 @@ A Flutter plugin for OpenAI services.
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.dependency 'Flutter' s.dependency 'Flutter'
s.platform = :ios, '13.0' s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice. # Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0' s.swift_version = '5.0'
# 添加系统框架依赖
s.frameworks = 'Foundation', 'UIKit', 'Contacts', 'EventKit', 'CoreLocation', 'MediaPlayer', 'AVFoundation'
# 添加权限描述
s.info_plist = {
'NSLocationWhenInUseUsageDescription' => 'This app needs location access to provide location-based services.',
'NSContactsUsageDescription' => 'This app needs access to contacts to make calls and send messages.',
'NSCalendarsUsageDescription' => 'This app needs access to calendar to set reminders.',
'NSRemindersUsageDescription' => 'This app needs access to reminders to create reminders.',
'NSMicrophoneUsageDescription' => 'This app needs microphone access for voice recording.'
}
end end

2
local_plugins/open_ai_service/lib/open_ai_service.dart

@ -79,7 +79,7 @@ class OpenAIService {
// 监听原生事件 // 监听原生事件
_eventChannel.receiveBroadcastStream().listen( _eventChannel.receiveBroadcastStream().listen(
(dynamic event) { (dynamic event) {
print("收到事件 OpenAIEvent: $event"); // 收到事件 OpenAIEvent: $event
if (event is Map<dynamic, dynamic>) { if (event is Map<dynamic, dynamic>) {
final eventMap = Map<String, dynamic>.from(event); final eventMap = Map<String, dynamic>.from(event);
final openAIEvent = OpenAIEvent.fromMap(eventMap); final openAIEvent = OpenAIEvent.fromMap(eventMap);

BIN
local_plugins/opus/.gradle/8.10/checksums/checksums.lock

Binary file not shown.

BIN
local_plugins/opus/.gradle/8.10/fileChanges/last-build.bin

Binary file not shown.

BIN
local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.bin

Binary file not shown.

BIN
local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock

Binary file not shown.

BIN
local_plugins/opus/.gradle/8.9/checksums/checksums.lock

Binary file not shown.

BIN
local_plugins/opus/.gradle/8.9/fileChanges/last-build.bin

Binary file not shown.

BIN
local_plugins/opus/.gradle/8.9/fileHashes/fileHashes.lock

Binary file not shown.

BIN
local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock

Binary file not shown.

2
local_plugins/opus/.gradle/buildOutputCleanup/cache.properties

@ -1,2 +0,0 @@
#Tue May 20 17:39:11 IST 2025
gradle.version=8.9

1
local_plugins/opus/.gradle/nb-cache/trust/404236D79074CDA6A1B2372C665DCA9E587766B94C3493E9904CD4249D51B6BF

@ -1 +0,0 @@
C7E6339E39539FBB82728828DAB8AC59D6F180C3E383F85B403CD9B85CA1DF6A

0
local_plugins/opus/.gradle/vcs-1/gc.properties

92
local_plugins/opus/README.md

@ -1,92 +0,0 @@
# Opus Plugin for Flutter
一个用于在 Flutter 中使用 Opus 音频编解码器的插件,支持 Android 和 iOS 平台。
## 功能
- Opus 编码:将 PCM 音频数据编码为 Opus 格式
- Opus 解码:将 Opus 数据解码为 PCM 音频
- 支持灵活设置编码参数:采样率、声道数、应用类型、复杂度等
- 兼容 Android 和 iOS 平台
## 使用方法
### 添加依赖
由于这是本地插件,在 `pubspec.yaml` 中添加:
```yaml
dependencies:
opus:
path: local_plugins/opus
```
### 初始化编解码器
```dart
// 创建编码器
await Opus.createEncoder(
sampleRate: 48000, // 采样率 (Hz)
channels: 2, // 声道数 (1=单声道, 2=立体声)
application: OpusApplicationType.audio, // 应用类型
complexity: 10, // 复杂度 (0-10)
frameSize: 960, // 帧大小
);
// 创建解码器
await Opus.createDecoder(
sampleRate: 48000,
channels: 2,
);
```
### 编码和解码
```dart
// 编码 PCM 数据
Uint8List pcmData = ...; // 16位 PCM 音频数据
Uint8List? opusData = await Opus.encodeData(pcmData);
// 解码 Opus 数据
Uint8List opusData = ...; // Opus 编码数据
Uint8List? pcmData = await Opus.decodeData(opusData, frameSize: 960);
```
### 释放资源
```dart
// 释放编码器资源
await Opus.disposeEncoder();
// 释放解码器资源
await Opus.disposeDecoder();
```
## 关于 Opus
Opus 是一种开源、免版税的音频编解码器,专为通过互联网传输音频而设计。它可变比特率从 6 kbit/s 到 510 kbit/s,帧大小从 2.5 ms 到 60 ms,支持采样率从 8 kHz(窄带)到 48 kHz(全频带)。
Opus 特点:
- 低延迟 (5-20ms)
- 高音质
- 灵活的比特率
- 支持语音和音乐
- 强大的丢包恢复能力
## 配置说明
### Android 配置
需要将 libopus.so 文件放在以下目录:
- android/src/main/jniLibs/armeabi-v7a/libopus.so
- android/src/main/jniLibs/arm64-v8a/libopus.so
> 注意:若需要自行编译 Opus,可参考 https://opus-codec.org/ 官方指南。
### iOS 配置
需要将 opus.framework 或 libopus.a 放在 ios/Frameworks 目录下。
## 示例
查看 [example](example) 目录中的示例代码,了解如何使用此插件生成、编码和解码音频数据。

21
local_plugins/opus/android/CMakeLists.txt

@ -1,21 +0,0 @@
cmake_minimum_required(VERSION 3.4.1)
# 设置opus源码路径,使用我们自己的头文件
set(OPUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/opus)
# 添加opus头文件路径
include_directories(${OPUS_DIR}/include)
# 创建我们自己的opus库(桩实现)
add_library(opus SHARED
src/main/cpp/opus_stub.cpp)
# 添加JNI库
add_library(opus_jni SHARED
src/main/cpp/opus_jni.cpp)
# 链接库
target_link_libraries(opus_jni
android
log
opus)

67
local_plugins/opus/android/build.gradle.kts

@ -1,67 +0,0 @@
plugins {
// Android Library 插件
id("com.android.library")
// Kotlin Android 插件
id("org.jetbrains.kotlin.android")
}
group = "com.yunqiinnovation.opus"
version = "1.0-SNAPSHOT"
android {
namespace = "com.yunqiinnovation.opus"
// 目标 SDK 版本
compileSdk = 35
defaultConfig {
// 最低 SDK 版本
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// 设置启用CMake构建
externalNativeBuild {
cmake {
arguments += "-DANDROID_STL=c++_shared"
cppFlags += "-std=c++11"
}
}
// 设置支持的ABIs
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
}
// Java 语言级别兼容配置
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// Kotlin 语言级别
kotlinOptions {
jvmTarget = "1.8"
}
// CMake配置
externalNativeBuild {
cmake {
path = file("CMakeLists.txt")
}
}
// 处理jniLibs
sourceSets {
getByName("main") {
jniLibs.srcDirs("src/main/jniLibs")
java.srcDir("src/main/kotlin")
}
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10")
implementation("androidx.annotation:annotation:1.3.0")
}

6
local_plugins/opus/android/libs/opus/AUTHORS

@ -1,6 +0,0 @@
Jean-Marc Valin (jmvalin@jmvalin.ca)
Koen Vos (koenvos74@gmail.com)
Timothy Terriberry (tterribe@xiph.org)
Karsten Vandborg Sorensen (karsten.vandborg.sorensen@skype.net)
Soren Skak Jensen (ssjensen@gn.com)
Gregory Maxwell (greg@xiph.org)

744
local_plugins/opus/android/libs/opus/CMakeLists.txt

@ -1,744 +0,0 @@
cmake_minimum_required(VERSION 3.16)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(OpusPackageVersion)
get_package_version(PACKAGE_VERSION PROJECT_VERSION)
project(Opus LANGUAGES C VERSION ${PROJECT_VERSION})
include(OpusFunctions)
include(OpusBuildtype)
include(OpusConfig)
include(OpusSources)
include(GNUInstallDirs)
include(CMakeDependentOption)
include(FeatureSummary)
set(OPUS_BUILD_SHARED_LIBRARY_HELP_STR "build shared library.")
option(OPUS_BUILD_SHARED_LIBRARY ${OPUS_BUILD_SHARED_LIBRARY_HELP_STR} OFF)
if(OPUS_BUILD_SHARED_LIBRARY OR BUILD_SHARED_LIBS OR OPUS_BUILD_FRAMEWORK)
# Global flag to cause add_library() to create shared libraries if on.
set(BUILD_SHARED_LIBS ON)
set(OPUS_BUILD_SHARED_LIBRARY ON)
endif()
add_feature_info(OPUS_BUILD_SHARED_LIBRARY OPUS_BUILD_SHARED_LIBRARY ${OPUS_BUILD_SHARED_LIBRARY_HELP_STR})
set(OPUS_BUILD_TESTING_HELP_STR "build tests.")
option(OPUS_BUILD_TESTING ${OPUS_BUILD_TESTING_HELP_STR} OFF)
if(OPUS_BUILD_TESTING OR BUILD_TESTING)
set(OPUS_BUILD_TESTING ON)
set(BUILD_TESTING ON)
endif()
add_feature_info(OPUS_BUILD_TESTING OPUS_BUILD_TESTING ${OPUS_BUILD_TESTING_HELP_STR})
set(OPUS_CUSTOM_MODES_HELP_STR "enable non-Opus modes, e.g. 44.1 kHz & 2^n frames.")
option(OPUS_CUSTOM_MODES ${OPUS_CUSTOM_MODES_HELP_STR} OFF)
add_feature_info(OPUS_CUSTOM_MODES OPUS_CUSTOM_MODES ${OPUS_CUSTOM_MODES_HELP_STR})
set(OPUS_BUILD_PROGRAMS_HELP_STR "build programs.")
option(OPUS_BUILD_PROGRAMS ${OPUS_BUILD_PROGRAMS_HELP_STR} OFF)
add_feature_info(OPUS_BUILD_PROGRAMS OPUS_BUILD_PROGRAMS ${OPUS_BUILD_PROGRAMS_HELP_STR})
set(OPUS_DISABLE_INTRINSICS_HELP_STR "disable all intrinsics optimizations.")
option(OPUS_DISABLE_INTRINSICS ${OPUS_DISABLE_INTRINSICS_HELP_STR} OFF)
add_feature_info(OPUS_DISABLE_INTRINSICS OPUS_DISABLE_INTRINSICS ${OPUS_DISABLE_INTRINSICS_HELP_STR})
set(OPUS_FIXED_POINT_HELP_STR "compile as fixed-point (for machines without a fast enough FPU).")
option(OPUS_FIXED_POINT ${OPUS_FIXED_POINT_HELP_STR} OFF)
add_feature_info(OPUS_FIXED_POINT OPUS_FIXED_POINT ${OPUS_FIXED_POINT_HELP_STR})
set(OPUS_ENABLE_FLOAT_API_HELP_STR "compile with the floating point API (for machines with float library).")
option(OPUS_ENABLE_FLOAT_API ${OPUS_ENABLE_FLOAT_API_HELP_STR} ON)
add_feature_info(OPUS_ENABLE_FLOAT_API OPUS_ENABLE_FLOAT_API ${OPUS_ENABLE_FLOAT_API_HELP_STR})
set(OPUS_FLOAT_APPROX_HELP_STR "enable floating point approximations (Ensure your platform supports IEEE 754 before enabling).")
option(OPUS_FLOAT_APPROX ${OPUS_FLOAT_APPROX_HELP_STR} OFF)
add_feature_info(OPUS_FLOAT_APPROX OPUS_FLOAT_APPROX ${OPUS_FLOAT_APPROX_HELP_STR})
set(OPUS_ASSERTIONS_HELP_STR "additional software error checking.")
option(OPUS_ASSERTIONS ${OPUS_ASSERTIONS_HELP_STR} OFF)
add_feature_info(OPUS_ASSERTIONS OPUS_ASSERTIONS ${OPUS_ASSERTIONS_HELP_STR})
set(OPUS_HARDENING_HELP_STR "run-time checks that are cheap and safe for use in production.")
option(OPUS_HARDENING ${OPUS_HARDENING_HELP_STR} ON)
add_feature_info(OPUS_HARDENING OPUS_HARDENING ${OPUS_HARDENING_HELP_STR})
set(OPUS_FUZZING_HELP_STR "causes the encoder to make random decisions (do not use in production).")
option(OPUS_FUZZING ${OPUS_FUZZING_HELP_STR} OFF)
add_feature_info(OPUS_FUZZING OPUS_FUZZING ${OPUS_FUZZING_HELP_STR})
set(OPUS_CHECK_ASM_HELP_STR "enable bit-exactness checks between optimized and c implementations.")
option(OPUS_CHECK_ASM ${OPUS_CHECK_ASM_HELP_STR} OFF)
add_feature_info(OPUS_CHECK_ASM OPUS_CHECK_ASM ${OPUS_CHECK_ASM_HELP_STR})
set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging purposes.")
option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.")
option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
set(OPUS_DRED_HELP_STR "enable DRED.")
option(OPUS_DRED ${OPUS_DRED_HELP_STR} OFF)
add_feature_info(OPUS_DRED OPUS_DRED ${OPUS_DRED_HELP_STR})
set(OPUS_OSCE_HELP_STR "enable OSCE.")
option(OPUS_OSCE ${OPUS_OSCE_HELP_STR} OFF)
add_feature_info(OPUS_OSCE OPUS_OSCE ${OPUS_OSCE_HELP_STR})
if(APPLE)
set(OPUS_BUILD_FRAMEWORK_HELP_STR "build Framework bundle for Apple systems.")
option(OPUS_BUILD_FRAMEWORK ${OPUS_BUILD_FRAMEWORK_HELP_STR} OFF)
add_feature_info(OPUS_BUILD_FRAMEWORK OPUS_BUILD_FRAMEWORK ${OPUS_BUILD_FRAMEWORK_HELP_STR})
endif()
if(MSVC)
set(OPUS_STATIC_RUNTIME_HELP_STR "build with static runtime library.")
option(OPUS_STATIC_RUNTIME ${OPUS_STATIC_RUNTIME_HELP_STR} OFF)
add_feature_info(OPUS_STATIC_RUNTIME OPUS_STATIC_RUNTIME ${OPUS_STATIC_RUNTIME_HELP_STR})
endif()
set(OPUS_FIXED_POINT_DEBUG_HELP_STR "debug fixed-point implementation.")
cmake_dependent_option(OPUS_FIXED_POINT_DEBUG
${OPUS_FIXED_POINT_DEBUG_HELP_STR}
ON
"OPUS_FIXED_POINT; OPUS_FIXED_POINT_DEBUG"
OFF)
add_feature_info(OPUS_FIXED_POINT_DEBUG OPUS_FIXED_POINT_DEBUG ${OPUS_FIXED_POINT_DEBUG_HELP_STR})
set(OPUS_VAR_ARRAYS_HELP_STR "use variable length arrays for stack arrays.")
cmake_dependent_option(OPUS_VAR_ARRAYS
${OPUS_VAR_ARRAYS_HELP_STR}
ON
"VLA_SUPPORTED; NOT OPUS_USE_ALLOCA; NOT OPUS_NONTHREADSAFE_PSEUDOSTACK"
OFF)
add_feature_info(OPUS_VAR_ARRAYS OPUS_VAR_ARRAYS ${OPUS_VAR_ARRAYS_HELP_STR})
set(OPUS_USE_ALLOCA_HELP_STR "use alloca for stack arrays (on non-C99 compilers).")
cmake_dependent_option(OPUS_USE_ALLOCA
${OPUS_USE_ALLOCA_HELP_STR}
ON
"USE_ALLOCA_SUPPORTED; NOT OPUS_VAR_ARRAYS; NOT OPUS_NONTHREADSAFE_PSEUDOSTACK"
OFF)
add_feature_info(OPUS_USE_ALLOCA OPUS_USE_ALLOCA ${OPUS_USE_ALLOCA_HELP_STR})
set(OPUS_NONTHREADSAFE_PSEUDOSTACK_HELP_STR "use a non threadsafe pseudostack when neither variable length arrays or alloca is supported.")
cmake_dependent_option(OPUS_NONTHREADSAFE_PSEUDOSTACK
${OPUS_NONTHREADSAFE_PSEUDOSTACK_HELP_STR}
ON
"NOT OPUS_VAR_ARRAYS; NOT OPUS_USE_ALLOCA"
OFF)
add_feature_info(OPUS_NONTHREADSAFE_PSEUDOSTACK OPUS_NONTHREADSAFE_PSEUDOSTACK ${OPUS_NONTHREADSAFE_PSEUDOSTACK_HELP_STR})
set(OPUS_FAST_MATH_HELP_STR "enable fast math (unsupported and discouraged use, as code is not well tested with this build option).")
cmake_dependent_option(OPUS_FAST_MATH
${OPUS_FAST_MATH_HELP_STR}
ON
"OPUS_FLOAT_APPROX; OPUS_FAST_MATH; FAST_MATH_SUPPORTED"
OFF)
add_feature_info(OPUS_FAST_MATH OPUS_FAST_MATH ${OPUS_FAST_MATH_HELP_STR})
set(OPUS_STACK_PROTECTOR_HELP_STR "use stack protection.")
cmake_dependent_option(OPUS_STACK_PROTECTOR
${OPUS_STACK_PROTECTOR_HELP_STR}
ON
"STACK_PROTECTOR_SUPPORTED"
OFF)
add_feature_info(OPUS_STACK_PROTECTOR OPUS_STACK_PROTECTOR ${OPUS_STACK_PROTECTOR_HELP_STR})
if(NOT MSVC)
set(OPUS_FORTIFY_SOURCE_HELP_STR "add protection against buffer overflows.")
cmake_dependent_option(OPUS_FORTIFY_SOURCE
${OPUS_FORTIFY_SOURCE_HELP_STR}
ON
"FORTIFY_SOURCE_SUPPORTED"
OFF)
add_feature_info(OPUS_FORTIFY_SOURCE OPUS_FORTIFY_SOURCE ${OPUS_FORTIFY_SOURCE_HELP_STR})
endif()
if(MINGW AND (OPUS_FORTIFY_SOURCE OR OPUS_STACK_PROTECTOR))
# ssp lib is needed for security features for MINGW
list(APPEND OPUS_REQUIRED_LIBRARIES ssp)
endif()
if(OPUS_CPU_X86 OR OPUS_CPU_X64)
set(OPUS_X86_MAY_HAVE_SSE_HELP_STR "does runtime check for SSE1 support.")
cmake_dependent_option(OPUS_X86_MAY_HAVE_SSE
${OPUS_X86_MAY_HAVE_SSE_HELP_STR}
ON
"SSE1_SUPPORTED; NOT OPUS_DISABLE_INTRINSICS"
OFF)
add_feature_info(OPUS_X86_MAY_HAVE_SSE OPUS_X86_MAY_HAVE_SSE ${OPUS_X86_MAY_HAVE_SSE_HELP_STR})
set(OPUS_X86_MAY_HAVE_SSE2_HELP_STR "does runtime check for SSE2 support.")
cmake_dependent_option(OPUS_X86_MAY_HAVE_SSE2
${OPUS_X86_MAY_HAVE_SSE2_HELP_STR}
ON
"SSE2_SUPPORTED; NOT OPUS_DISABLE_INTRINSICS"
OFF)
add_feature_info(OPUS_X86_MAY_HAVE_SSE2 OPUS_X86_MAY_HAVE_SSE2 ${OPUS_X86_MAY_HAVE_SSE2_HELP_STR})
set(OPUS_X86_MAY_HAVE_SSE4_1_HELP_STR "does runtime check for SSE4.1 support.")
cmake_dependent_option(OPUS_X86_MAY_HAVE_SSE4_1
${OPUS_X86_MAY_HAVE_SSE4_1_HELP_STR}
ON
"SSE4_1_SUPPORTED; NOT OPUS_DISABLE_INTRINSICS"
OFF)
add_feature_info(OPUS_X86_MAY_HAVE_SSE4_1 OPUS_X86_MAY_HAVE_SSE4_1 ${OPUS_X86_MAY_HAVE_SSE4_1_HELP_STR})
set(OPUS_X86_MAY_HAVE_AVX2_HELP_STR "does runtime check for AVX FMA AVX2 support.")
cmake_dependent_option(OPUS_X86_MAY_HAVE_AVX2
${OPUS_X86_MAY_HAVE_AVX2_HELP_STR}
ON
"AVX2_SUPPORTED; NOT OPUS_DISABLE_INTRINSICS"
OFF)
add_feature_info(OPUS_X86_MAY_HAVE_AVX2 OPUS_X86_MAY_HAVE_AVX2 ${OPUS_X86_MAY_HAVE_AVX2_HELP_STR})
# PRESUME depends on MAY HAVE, but PRESUME will override runtime detection
set(OPUS_X86_PRESUME_SSE_HELP_STR "assume target CPU has SSE1 support (override runtime check).")
set(OPUS_X86_PRESUME_SSE2_HELP_STR "assume target CPU has SSE2 support (override runtime check).")
if(OPUS_CPU_X64) # Assume x86_64 has up to SSE2 support
cmake_dependent_option(OPUS_X86_PRESUME_SSE
${OPUS_X86_PRESUME_SSE_HELP_STR}
ON
"OPUS_X86_MAY_HAVE_SSE; NOT OPUS_DISABLE_INTRINSICS"
OFF)
cmake_dependent_option(OPUS_X86_PRESUME_SSE2
${OPUS_X86_PRESUME_SSE2_HELP_STR}
ON
"OPUS_X86_MAY_HAVE_SSE2; NOT OPUS_DISABLE_INTRINSICS"
OFF)
else()
cmake_dependent_option(OPUS_X86_PRESUME_SSE
${OPUS_X86_PRESUME_SSE_HELP_STR}
OFF
"OPUS_X86_MAY_HAVE_SSE; NOT OPUS_DISABLE_INTRINSICS"
OFF)
cmake_dependent_option(OPUS_X86_PRESUME_SSE2
${OPUS_X86_PRESUME_SSE2_HELP_STR}
OFF
"OPUS_X86_MAY_HAVE_SSE2; NOT OPUS_DISABLE_INTRINSICS"
OFF)
endif()
add_feature_info(OPUS_X86_PRESUME_SSE OPUS_X86_PRESUME_SSE ${OPUS_X86_PRESUME_SSE_HELP_STR})
add_feature_info(OPUS_X86_PRESUME_SSE2 OPUS_X86_PRESUME_SSE2 ${OPUS_X86_PRESUME_SSE2_HELP_STR})
set(OPUS_X86_PRESUME_SSE4_1_HELP_STR "assume target CPU has SSE4.1 support (override runtime check).")
cmake_dependent_option(OPUS_X86_PRESUME_SSE4_1
${OPUS_X86_PRESUME_SSE4_1_HELP_STR}
OFF
"OPUS_X86_MAY_HAVE_SSE4_1; NOT OPUS_DISABLE_INTRINSICS"
OFF)
add_feature_info(OPUS_X86_PRESUME_SSE4_1 OPUS_X86_PRESUME_SSE4_1 ${OPUS_X86_PRESUME_SSE4_1_HELP_STR})
set(OPUS_X86_PRESUME_AVX2_HELP_STR "assume target CPU has AVX FMA AVX2 support (override runtime check).")
cmake_dependent_option(OPUS_X86_PRESUME_AVX2
${OPUS_X86_PRESUME_AVX2_HELP_STR}
OFF
"OPUS_X86_MAY_HAVE_AVX2; NOT OPUS_DISABLE_INTRINSICS"
OFF)
add_feature_info(OPUS_X86_PRESUME_AVX2 OPUS_X86_PRESUME_AVX2 ${OPUS_X86_PRESUME_AVX2_HELP_STR})
endif()
feature_summary(WHAT ALL)
set_package_properties(Git
PROPERTIES
TYPE
REQUIRED
DESCRIPTION
"fast, scalable, distributed revision control system"
URL
"https://git-scm.com/"
PURPOSE
"required to set up package version")
set(Opus_PUBLIC_HEADER
${CMAKE_CURRENT_SOURCE_DIR}/include/opus.h
${CMAKE_CURRENT_SOURCE_DIR}/include/opus_defines.h
${CMAKE_CURRENT_SOURCE_DIR}/include/opus_multistream.h
${CMAKE_CURRENT_SOURCE_DIR}/include/opus_projection.h
${CMAKE_CURRENT_SOURCE_DIR}/include/opus_types.h)
if(OPUS_CUSTOM_MODES)
list(APPEND Opus_PUBLIC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/opus_custom.h)
endif()
if(MSVC)
if(OPUS_STATIC_RUNTIME)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()
endif()
add_library(opus ${opus_headers} ${opus_sources} ${opus_sources_float} ${Opus_PUBLIC_HEADER})
add_library(Opus::opus ALIAS opus)
get_library_version(OPUS_LIBRARY_VERSION OPUS_LIBRARY_VERSION_MAJOR)
message(DEBUG "Opus library version: ${OPUS_LIBRARY_VERSION}")
set_target_properties(opus
PROPERTIES SOVERSION
${OPUS_LIBRARY_VERSION_MAJOR}
VERSION
${OPUS_LIBRARY_VERSION}
PUBLIC_HEADER
"${Opus_PUBLIC_HEADER}")
target_include_directories(
opus
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/opus>
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/dnn
celt
silk)
target_link_libraries(opus PRIVATE ${OPUS_REQUIRED_LIBRARIES})
target_compile_definitions(opus PRIVATE OPUS_BUILD)
if(OPUS_FIXED_POINT_DEBUG)
target_compile_definitions(opus PRIVATE FIXED_DEBUG)
endif()
if(OPUS_FORTIFY_SOURCE AND NOT MSVC)
target_compile_definitions(opus PRIVATE
$<$<NOT:$<CONFIG:debug>>:_FORTIFY_SOURCE=2>)
endif()
if(OPUS_FLOAT_APPROX)
target_compile_definitions(opus PRIVATE FLOAT_APPROX)
endif()
if(OPUS_ASSERTIONS)
target_compile_definitions(opus PRIVATE ENABLE_ASSERTIONS)
endif()
if(OPUS_HARDENING)
target_compile_definitions(opus PRIVATE ENABLE_HARDENING)
endif()
if(OPUS_FUZZING)
target_compile_definitions(opus PRIVATE FUZZING)
endif()
if(OPUS_CHECK_ASM)
target_compile_definitions(opus PRIVATE OPUS_CHECK_ASM)
endif()
if(NOT OPUS_DNN_FLOAT_DEBUG)
target_compile_definitions(opus PRIVATE DISABLE_DEBUG_FLOAT)
endif()
if(OPUS_VAR_ARRAYS)
target_compile_definitions(opus PRIVATE VAR_ARRAYS)
elseif(OPUS_USE_ALLOCA)
target_compile_definitions(opus PRIVATE USE_ALLOCA)
elseif(OPUS_NONTHREADSAFE_PSEUDOSTACK)
target_compile_definitions(opus PRIVATE NONTHREADSAFE_PSEUDOSTACK)
else()
message(ERROR "Need to set a define for stack allocation")
endif()
if(OPUS_CUSTOM_MODES)
target_compile_definitions(opus PRIVATE CUSTOM_MODES)
endif()
if(OPUS_FAST_MATH)
if(MSVC)
target_compile_options(opus PRIVATE /fp:fast)
else()
target_compile_options(opus PRIVATE -ffast-math)
endif()
endif()
if(OPUS_STACK_PROTECTOR)
if(MSVC)
target_compile_options(opus PRIVATE /GS)
else()
target_compile_options(opus PRIVATE -fstack-protector-strong)
endif()
elseif(STACK_PROTECTOR_DISABLED_SUPPORTED)
target_compile_options(opus PRIVATE /GS-)
endif()
if(BUILD_SHARED_LIBS)
if(WIN32)
target_compile_definitions(opus PRIVATE DLL_EXPORT)
elseif(HIDDEN_VISIBILITY_SUPPORTED)
set_target_properties(opus PROPERTIES C_VISIBILITY_PRESET hidden)
endif()
endif()
add_sources_group(opus silk ${silk_headers} ${silk_sources})
add_sources_group(opus celt ${celt_headers} ${celt_sources})
if(OPUS_FIXED_POINT)
add_sources_group(opus silk ${silk_sources_fixed})
target_include_directories(opus PRIVATE silk/fixed)
target_compile_definitions(opus PRIVATE FIXED_POINT=1)
else()
add_sources_group(opus silk ${silk_sources_float})
target_include_directories(opus PRIVATE silk/float)
endif()
if(NOT OPUS_ENABLE_FLOAT_API)
target_compile_definitions(opus PRIVATE DISABLE_FLOAT_API)
endif()
if (OPUS_DEEP_PLC OR OPUS_DRED OR OPUS_OSCE)
add_sources_group(opus lpcnet ${deep_plc_headers} ${deep_plc_sources})
set(OPUS_DNN TRUE)
else()
set(OPUS_DNN FALSE)
endif()
if (OPUS_DNN)
add_sources_group(opus lpcnet ${deep_plc_headers} ${deep_plc_sources})
target_compile_definitions(opus PRIVATE ENABLE_DEEP_PLC)
endif()
if (OPUS_DRED)
add_sources_group(opus lpcnet ${dred_headers} ${dred_sources})
target_compile_definitions(opus PRIVATE ENABLE_DRED)
endif()
if (OPUS_OSCE)
add_sources_group(opus lpcnet ${osce_headers} ${osce_sources})
target_compile_definitions(opus PRIVATE ENABLE_OSCE)
endif()
if(NOT OPUS_DISABLE_INTRINSICS)
if(((OPUS_X86_MAY_HAVE_SSE AND NOT OPUS_X86_PRESUME_SSE) OR
(OPUS_X86_MAY_HAVE_SSE2 AND NOT OPUS_X86_PRESUME_SSE2) OR
(OPUS_X86_MAY_HAVE_SSE4_1 AND NOT OPUS_X86_PRESUME_SSE4_1) OR
(OPUS_X86_MAY_HAVE_AVX2 AND NOT OPUS_X86_PRESUME_AVX2)) AND
RUNTIME_CPU_CAPABILITY_DETECTION)
target_compile_definitions(opus PRIVATE OPUS_HAVE_RTCD)
if(NOT MSVC)
if(CPU_INFO_BY_ASM_SUPPORTED)
target_compile_definitions(opus PRIVATE CPU_INFO_BY_ASM)
elseif(CPU_INFO_BY_C_SUPPORTED)
target_compile_definitions(opus PRIVATE CPU_INFO_BY_C)
else()
message(ERROR "Runtime cpu capability detection is enabled while CPU_INFO is not supported")
endif()
endif()
add_sources_group(opus celt ${celt_sources_x86_rtcd})
add_sources_group(opus silk ${silk_sources_x86_rtcd})
if (OPUS_DNN)
add_sources_group(opus lpcnet ${dnn_sources_x86_rtcd})
endif()
endif()
if(SSE1_SUPPORTED)
if(OPUS_X86_MAY_HAVE_SSE)
add_sources_group(opus celt ${celt_sources_sse})
target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_SSE)
if(NOT MSVC)
set_source_files_properties(${celt_sources_sse} PROPERTIES COMPILE_FLAGS -msse)
endif()
endif()
if(OPUS_X86_PRESUME_SSE)
target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_SSE)
if(NOT MSVC)
target_compile_options(opus PRIVATE -msse)
endif()
endif()
endif()
if(SSE2_SUPPORTED)
if(OPUS_X86_MAY_HAVE_SSE2)
add_sources_group(opus celt ${celt_sources_sse2})
if (OPUS_DNN)
add_sources_group(opus lpcnet ${dnn_sources_sse2})
endif()
target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_SSE2)
if(NOT MSVC)
set_source_files_properties(${celt_sources_sse2} ${dnn_sources_sse2} PROPERTIES COMPILE_FLAGS -msse2)
endif()
endif()
if(OPUS_X86_PRESUME_SSE2)
target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_SSE2)
if(NOT MSVC)
target_compile_options(opus PRIVATE -msse2)
endif()
endif()
endif()
if(SSE4_1_SUPPORTED)
if(OPUS_X86_MAY_HAVE_SSE4_1)
add_sources_group(opus celt ${celt_sources_sse4_1})
add_sources_group(opus silk ${silk_sources_sse4_1})
if (OPUS_DNN)
add_sources_group(opus lpcnet ${dnn_sources_sse4_1})
endif()
target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_SSE4_1)
if(NOT MSVC)
set_source_files_properties(${celt_sources_sse4_1} ${silk_sources_sse4_1} ${dnn_sources_sse4_1} PROPERTIES COMPILE_FLAGS -msse4.1)
endif()
if(OPUS_FIXED_POINT)
add_sources_group(opus silk ${silk_sources_fixed_sse4_1})
if(NOT MSVC)
set_source_files_properties(${silk_sources_fixed_sse4_1} PROPERTIES COMPILE_FLAGS -msse4.1)
endif()
endif()
endif()
if(OPUS_X86_PRESUME_SSE4_1)
target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_SSE4_1)
if(NOT MSVC)
target_compile_options(opus PRIVATE -msse4.1)
endif()
endif()
endif()
if(AVX2_SUPPORTED)
if(OPUS_X86_MAY_HAVE_AVX2)
add_sources_group(opus celt ${celt_sources_avx2})
add_sources_group(opus silk ${silk_sources_avx2})
add_sources_group(opus silk ${silk_sources_float_avx2})
if (OPUS_DNN)
add_sources_group(opus lpcnet ${dnn_sources_avx2})
endif()
target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_AVX2)
if(MSVC)
set(AVX2_FLAGS "${AVX2_FLAGS} /arch:AVX2")
else()
set(AVX2_FLAGS "${AVX2_FLAGS} -mavx2 -mfma -mavx")
endif()
set_source_files_properties(${celt_sources_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
set_source_files_properties(${silk_sources_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
set_source_files_properties(${silk_sources_float_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
set_source_files_properties(${dnn_sources_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
endif()
if(OPUS_X86_PRESUME_AVX2)
target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_AVX2)
target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_SSE4_1)
if(NOT MSVC)
target_compile_options(opus PRIVATE -mavx2 -mfma -mavx)
endif()
endif()
endif()
if(MSVC)
if(AVX2_SUPPORTED AND OPUS_X86_PRESUME_AVX2) # on 64 bit and 32 bits
add_definitions(/arch:AVX2)
elseif(OPUS_CPU_X86) # if AVX not supported then set SSE flag
if((SSE4_1_SUPPORTED AND OPUS_X86_PRESUME_SSE4_1)
OR (SSE2_SUPPORTED AND OPUS_X86_PRESUME_SSE2))
target_compile_definitions(opus PRIVATE /arch:SSE2)
elseif(SSE1_SUPPORTED AND OPUS_X86_PRESUME_SSE)
target_compile_definitions(opus PRIVATE /arch:SSE)
endif()
endif()
endif()
if(COMPILER_SUPPORT_NEON)
if(OPUS_MAY_HAVE_NEON)
if(RUNTIME_CPU_CAPABILITY_DETECTION)
message(STATUS "OPUS_MAY_HAVE_NEON enabling runtime detection")
target_compile_definitions(opus PRIVATE OPUS_HAVE_RTCD)
add_sources_group(opus celt ${celt_sources_arm_rtcd})
add_sources_group(opus silk ${silk_sources_arm_rtcd})
else()
message(ERROR "Runtime cpu capability detection needed for MAY_HAVE_NEON")
endif()
# Do runtime check for NEON
target_compile_definitions(opus
PRIVATE
OPUS_ARM_MAY_HAVE_NEON
OPUS_ARM_MAY_HAVE_NEON_INTR)
endif()
add_sources_group(opus celt ${celt_sources_arm_neon_intr})
add_sources_group(opus silk ${silk_sources_arm_neon_intr})
if (OPUS_DNN)
add_sources_group(opus lpcnet ${dnn_sources_arm_neon})
endif()
# silk arm neon depends on main_Fix.h
target_include_directories(opus PRIVATE silk/fixed)
if(OPUS_FIXED_POINT)
add_sources_group(opus silk ${silk_sources_fixed_arm_neon_intr})
endif()
if(OPUS_PRESUME_NEON)
target_compile_definitions(opus
PRIVATE
OPUS_ARM_PRESUME_NEON
OPUS_ARM_PRESUME_NEON_INTR)
endif()
endif()
endif()
target_compile_definitions(opus
PRIVATE
$<$<BOOL:${HAVE_LRINT}>:HAVE_LRINT>
$<$<BOOL:${HAVE_LRINTF}>:HAVE_LRINTF>)
if(OPUS_BUILD_FRAMEWORK)
set_target_properties(opus PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION ${PROJECT_VERSION}
MACOSX_FRAMEWORK_IDENTIFIER org.xiph.opus
MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PROJECT_VERSION}
MACOSX_FRAMEWORK_BUNDLE_VERSION ${PROJECT_VERSION}
XCODE_ATTRIBUTE_INSTALL_PATH "@rpath"
OUTPUT_NAME Opus)
endif()
install(TARGETS opus
EXPORT OpusTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
if(OPUS_INSTALL_PKG_CONFIG_MODULE)
set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix ${CMAKE_INSTALL_PREFIX})
set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(VERSION ${PACKAGE_VERSION})
if(HAVE_LIBM)
set(LIBM "-lm")
endif()
configure_file(opus.pc.in opus.pc)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()
if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
set(CPACK_GENERATOR TGZ)
include(CPack)
set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(EXPORT OpusTargets
NAMESPACE Opus::
DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
include(CMakePackageConfigHelpers)
set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
OpusConfig.cmake
INSTALL_DESTINATION
${CMAKE_INSTALL_PACKAGEDIR}
PATH_VARS
INCLUDE_INSTALL_DIR
INSTALL_PREFIX
${CMAKE_INSTALL_PREFIX})
write_basic_package_version_file(OpusConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
endif()
if(OPUS_BUILD_PROGRAMS)
# demo
if(OPUS_CUSTOM_MODES)
add_executable(opus_custom_demo ${opus_custom_demo_sources})
target_include_directories(opus_custom_demo
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(opus_custom_demo PRIVATE opus)
target_compile_definitions(opus_custom_demo PRIVATE OPUS_BUILD)
endif()
add_executable(opus_demo ${opus_demo_sources})
target_include_directories(opus_demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(opus_demo PRIVATE silk) # debug.h
target_include_directories(opus_demo PRIVATE celt) # arch.h
target_include_directories(opus_demo PRIVATE dnn)
target_link_libraries(opus_demo PRIVATE opus ${OPUS_REQUIRED_LIBRARIES})
target_compile_definitions(opus_demo PRIVATE OPUS_BUILD)
# compare
add_executable(opus_compare ${opus_compare_sources})
target_include_directories(opus_compare PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(opus_compare PRIVATE opus ${OPUS_REQUIRED_LIBRARIES})
endif()
if(BUILD_TESTING AND NOT BUILD_SHARED_LIBS)
enable_testing()
# tests
add_executable(test_opus_decode ${test_opus_decode_sources})
target_include_directories(test_opus_decode
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(test_opus_decode PRIVATE opus)
target_compile_definitions(test_opus_decode PRIVATE OPUS_BUILD)
if(OPUS_FIXED_POINT)
target_compile_definitions(test_opus_decode PRIVATE DISABLE_FLOAT_API)
endif()
add_test(NAME test_opus_decode COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_decode>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
add_executable(test_opus_padding ${test_opus_padding_sources})
target_include_directories(test_opus_padding
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(test_opus_padding PRIVATE opus)
add_test(NAME test_opus_padding COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_padding>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
add_executable(test_opus_api ${test_opus_api_sources})
target_include_directories(test_opus_api
PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt)
target_link_libraries(test_opus_api PRIVATE opus)
target_compile_definitions(test_opus_api PRIVATE OPUS_BUILD)
if(OPUS_FIXED_POINT)
target_compile_definitions(test_opus_api PRIVATE DISABLE_FLOAT_API)
endif()
add_test(NAME test_opus_api COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_api>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
add_executable(test_opus_encode ${test_opus_encode_sources})
target_include_directories(test_opus_encode
PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt dnn)
target_link_libraries(test_opus_encode PRIVATE opus)
target_compile_definitions(test_opus_encode PRIVATE OPUS_BUILD)
add_test(NAME test_opus_encode COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_encode>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
add_executable(test_opus_extensions ${test_opus_extensions_sources})
target_include_directories(test_opus_extensions
PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt dnn)
target_link_libraries(test_opus_extensions PRIVATE opus)
target_compile_definitions(test_opus_extensions PRIVATE OPUS_BUILD)
add_test(NAME test_opus_extensions COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_extensions>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
if(OPUS_DRED)
add_executable(test_opus_dred ${test_opus_dred_sources})
target_include_directories(test_opus_dred
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(test_opus_dred PRIVATE opus)
target_compile_definitions(test_opus_dred PRIVATE OPUS_BUILD)
add_test(NAME test_opus_dred COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_dred>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
endif()
endif()

44
local_plugins/opus/android/libs/opus/COPYING

@ -1,44 +0,0 @@
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
Jean-Marc Valin, Timothy B. Terriberry,
CSIRO, Gregory Maxwell, Mark Borgerding,
Erik de Castro Lopo, Mozilla, Amazon
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Opus is subject to the royalty-free patent licenses which are
specified at:
Xiph.Org Foundation:
https://datatracker.ietf.org/ipr/1524/
Microsoft Corporation:
https://datatracker.ietf.org/ipr/1914/
Broadcom Corporation:
https://datatracker.ietf.org/ipr/1526/

0
local_plugins/opus/android/libs/opus/ChangeLog

368
local_plugins/opus/android/libs/opus/INSTALL

@ -1,368 +0,0 @@
Installation Instructions
*************************
Copyright (C) 1994-1996, 1999-2002, 2004-2017, 2020-2021 Free
Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
Basic Installation
==================
Briefly, the shell command './configure && make && make install'
should configure, build, and install this package. The following
more-detailed instructions are generic; see the 'README' file for
instructions specific to this package. Some packages provide this
'INSTALL' file but do not implement all of the features documented
below. The lack of an optional feature in a given package is not
necessarily a bug. More recommendations for GNU packages can be found
in *note Makefile Conventions: (standards)Makefile Conventions.
The 'configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a 'Makefile' in each directory of the package.
It may also create one or more '.h' files containing system-dependent
definitions. Finally, it creates a shell script 'config.status' that
you can run in the future to recreate the current configuration, and a
file 'config.log' containing compiler output (useful mainly for
debugging 'configure').
It can also use an optional file (typically called 'config.cache' and
enabled with '--cache-file=config.cache' or simply '-C') that saves the
results of its tests to speed up reconfiguring. Caching is disabled by
default to prevent problems with accidental use of stale cache files.
If you need to do unusual things to compile the package, please try
to figure out how 'configure' could check whether to do them, and mail
diffs or instructions to the address given in the 'README' so they can
be considered for the next release. If you are using the cache, and at
some point 'config.cache' contains results you don't want to keep, you
may remove or edit it.
The file 'configure.ac' (or 'configure.in') is used to create
'configure' by a program called 'autoconf'. You need 'configure.ac' if
you want to change it or regenerate 'configure' using a newer version of
'autoconf'.
The simplest way to compile this package is:
1. 'cd' to the directory containing the package's source code and type
'./configure' to configure the package for your system.
Running 'configure' might take a while. While running, it prints
some messages telling which features it is checking for.
2. Type 'make' to compile the package.
3. Optionally, type 'make check' to run any self-tests that come with
the package, generally using the just-built uninstalled binaries.
4. Type 'make install' to install the programs and any data files and
documentation. When installing into a prefix owned by root, it is
recommended that the package be configured and built as a regular
user, and only the 'make install' phase executed with root
privileges.
5. Optionally, type 'make installcheck' to repeat any self-tests, but
this time using the binaries in their final installed location.
This target does not install anything. Running this target as a
regular user, particularly if the prior 'make install' required
root privileges, verifies that the installation completed
correctly.
6. You can remove the program binaries and object files from the
source code directory by typing 'make clean'. To also remove the
files that 'configure' created (so you can compile the package for
a different kind of computer), type 'make distclean'. There is
also a 'make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
7. Often, you can also type 'make uninstall' to remove the installed
files again. In practice, not all packages have tested that
uninstallation works correctly, even though it is required by the
GNU Coding Standards.
8. Some packages, particularly those that use Automake, provide 'make
distcheck', which can by used by developers to test that all other
targets like 'make install' and 'make uninstall' work correctly.
This target is generally not run by end users.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the 'configure' script does not know about. Run './configure --help'
for details on some of the pertinent environment variables.
You can give 'configure' initial values for configuration parameters
by setting variables in the command line or in the environment. Here is
an example:
./configure CC=c99 CFLAGS=-g LIBS=-lposix
*Note Defining Variables::, for more details.
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you can use GNU 'make'. 'cd' to the
directory where you want the object files and executables to go and run
the 'configure' script. 'configure' automatically checks for the source
code in the directory that 'configure' is in and in '..'. This is known
as a "VPATH" build.
With a non-GNU 'make', it is safer to compile the package for one
architecture at a time in the source code directory. After you have
installed the package for one architecture, use 'make distclean' before
reconfiguring for another architecture.
On MacOS X 10.5 and later systems, you can create libraries and
executables that work on multiple system types--known as "fat" or
"universal" binaries--by specifying multiple '-arch' options to the
compiler but only a single '-arch' option to the preprocessor. Like
this:
./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CPP="gcc -E" CXXCPP="g++ -E"
This is not guaranteed to produce working output in all cases, you
may have to build one architecture at a time and combine the results
using the 'lipo' tool if you have problems.
Installation Names
==================
By default, 'make install' installs the package's commands under
'/usr/local/bin', include files under '/usr/local/include', etc. You
can specify an installation prefix other than '/usr/local' by giving
'configure' the option '--prefix=PREFIX', where PREFIX must be an
absolute file name.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
pass the option '--exec-prefix=PREFIX' to 'configure', the package uses
PREFIX as the prefix for installing programs and libraries.
Documentation and other data files still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like '--bindir=DIR' to specify different values for particular
kinds of files. Run 'configure --help' for a list of the directories
you can set and what kinds of files go in them. In general, the default
for these options is expressed in terms of '${prefix}', so that
specifying just '--prefix' will affect all of the other directory
specifications that were not explicitly provided.
The most portable way to affect installation locations is to pass the
correct locations to 'configure'; however, many packages provide one or
both of the following shortcuts of passing variable assignments to the
'make install' command line to change installation locations without
having to reconfigure or recompile.
The first method involves providing an override variable for each
affected directory. For example, 'make install
prefix=/alternate/directory' will choose an alternate location for all
directory configuration variables that were expressed in terms of
'${prefix}'. Any directories that were specified during 'configure',
but not in terms of '${prefix}', must each be overridden at install time
for the entire installation to be relocated. The approach of makefile
variable overrides for each directory variable is required by the GNU
Coding Standards, and ideally causes no recompilation. However, some
platforms have known limitations with the semantics of shared libraries
that end up requiring recompilation when using this method, particularly
noticeable in packages that use GNU Libtool.
The second method involves providing the 'DESTDIR' variable. For
example, 'make install DESTDIR=/alternate/directory' will prepend
'/alternate/directory' before all installation names. The approach of
'DESTDIR' overrides is not required by the GNU Coding Standards, and
does not work on platforms that have drive letters. On the other hand,
it does better at avoiding recompilation issues, and works well even
when some directory options were not specified in terms of '${prefix}'
at 'configure' time.
Optional Features
=================
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving 'configure' the
option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'.
Some packages pay attention to '--enable-FEATURE' options to
'configure', where FEATURE indicates an optional part of the package.
They may also pay attention to '--with-PACKAGE' options, where PACKAGE
is something like 'gnu-as' or 'x' (for the X Window System). The
'README' should mention any '--enable-' and '--with-' options that the
package recognizes.
For packages that use the X Window System, 'configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the 'configure' options '--x-includes=DIR' and
'--x-libraries=DIR' to specify their locations.
Some packages offer the ability to configure how verbose the
execution of 'make' will be. For these packages, running './configure
--enable-silent-rules' sets the default to minimal output, which can be
overridden with 'make V=1'; while running './configure
--disable-silent-rules' sets the default to verbose, which can be
overridden with 'make V=0'.
Particular systems
==================
On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC
is not installed, it is recommended to use the following options in
order to use an ANSI C compiler:
./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
and if that doesn't work, install pre-built binaries of GCC for HP-UX.
HP-UX 'make' updates targets which have the same timestamps as their
prerequisites, which makes it generally unusable when shipped generated
files such as 'configure' are involved. Use GNU 'make' instead.
On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
parse its '<wchar.h>' header file. The option '-nodtk' can be used as a
workaround. If GNU CC is not installed, it is therefore recommended to
try
./configure CC="cc"
and if that doesn't work, try
./configure CC="cc -nodtk"
On Solaris, don't put '/usr/ucb' early in your 'PATH'. This
directory contains several dysfunctional programs; working variants of
these programs are available in '/usr/bin'. So, if you need '/usr/ucb'
in your 'PATH', put it _after_ '/usr/bin'.
On Haiku, software installed for all users goes in '/boot/common',
not '/usr/local'. It is recommended to use the following options:
./configure --prefix=/boot/common
Specifying the System Type
==========================
There may be some features 'configure' cannot figure out
automatically, but needs to determine by the type of machine the package
will run on. Usually, assuming the package is built to be run on the
_same_ architectures, 'configure' can figure that out, but if it prints
a message saying it cannot guess the machine type, give it the
'--build=TYPE' option. TYPE can either be a short name for the system
type, such as 'sun4', or a canonical name which has the form:
CPU-COMPANY-SYSTEM
where SYSTEM can have one of these forms:
OS
KERNEL-OS
See the file 'config.sub' for the possible values of each field. If
'config.sub' isn't included in this package, then this package doesn't
need to know the machine type.
If you are _building_ compiler tools for cross-compiling, you should
use the option '--target=TYPE' to select the type of system they will
produce code for.
If you want to _use_ a cross compiler, that generates code for a
platform different from the build platform, you should specify the
"host" platform (i.e., that on which the generated programs will
eventually be run) with '--host=TYPE'.
Sharing Defaults
================
If you want to set default values for 'configure' scripts to share,
you can create a site shell script called 'config.site' that gives
default values for variables like 'CC', 'cache_file', and 'prefix'.
'configure' looks for 'PREFIX/share/config.site' if it exists, then
'PREFIX/etc/config.site' if it exists. Or, you can set the
'CONFIG_SITE' environment variable to the location of the site script.
A warning: not all 'configure' scripts look for a site script.
Defining Variables
==================
Variables not defined in a site shell script can be set in the
environment passed to 'configure'. However, some packages may run
configure again during the build, and the customized values of these
variables may be lost. In order to avoid this problem, you should set
them in the 'configure' command line, using 'VAR=value'. For example:
./configure CC=/usr/local2/bin/gcc
causes the specified 'gcc' to be used as the C compiler (unless it is
overridden in the site shell script).
Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an
Autoconf limitation. Until the limitation is lifted, you can use this
workaround:
CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash
'configure' Invocation
======================
'configure' recognizes the following options to control how it
operates.
'--help'
'-h'
Print a summary of all of the options to 'configure', and exit.
'--help=short'
'--help=recursive'
Print a summary of the options unique to this package's
'configure', and exit. The 'short' variant lists options used only
in the top level, while the 'recursive' variant lists options also
present in any nested packages.
'--version'
'-V'
Print the version of Autoconf used to generate the 'configure'
script, and exit.
'--cache-file=FILE'
Enable the cache: use and save the results of the tests in FILE,
traditionally 'config.cache'. FILE defaults to '/dev/null' to
disable caching.
'--config-cache'
'-C'
Alias for '--cache-file=config.cache'.
'--quiet'
'--silent'
'-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to '/dev/null' (any error
messages will still be shown).
'--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
'configure' can determine that directory automatically.
'--prefix=DIR'
Use DIR as the installation prefix. *note Installation Names:: for
more details, including other options available for fine-tuning the
installation locations.
'--no-create'
'-n'
Run the configure checks, but stop before creating any output
files.
'configure' also accepts some other, not widely useful, options. Run
'configure --help' for more details.

489
local_plugins/opus/android/libs/opus/Makefile.am

@ -1,489 +0,0 @@
# Provide the full test output for failed tests when using the parallel
# test suite (which is enabled by default with automake 1.13+).
export VERBOSE = yes
AUTOMAKE_OPTIONS = subdir-objects
ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libopus.la
DIST_SUBDIRS = doc
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/celt -I$(top_srcdir)/silk \
-I$(top_srcdir)/silk/float -I$(top_srcdir)/silk/fixed $(NE10_CFLAGS) \
-I$(top_srcdir)/dnn
include celt_sources.mk
include lpcnet_sources.mk
include silk_sources.mk
include opus_sources.mk
LPCNET_SOURCES =
if ENABLE_DEEP_PLC
LPCNET_SOURCES += $(DEEP_PLC_SOURCES)
endif
if ENABLE_DRED
LPCNET_SOURCES += $(DRED_SOURCES)
endif
if ENABLE_OSCE
LPCNET_SOURCES += $(OSCE_SOURCES)
endif
if FIXED_POINT
SILK_SOURCES += $(SILK_SOURCES_FIXED)
if HAVE_SSE4_1
SILK_SOURCES += $(SILK_SOURCES_SSE4_1) $(SILK_SOURCES_FIXED_SSE4_1)
endif
if HAVE_ARM_NEON_INTR
SILK_SOURCES += $(SILK_SOURCES_FIXED_ARM_NEON_INTR)
endif
else
SILK_SOURCES += $(SILK_SOURCES_FLOAT)
if HAVE_SSE4_1
SILK_SOURCES += $(SILK_SOURCES_SSE4_1)
endif
if HAVE_AVX2
SILK_SOURCES += $(SILK_SOURCES_FLOAT_AVX2)
endif
endif
if DISABLE_FLOAT_API
else
OPUS_SOURCES += $(OPUS_SOURCES_FLOAT)
endif
if CPU_X86
if HAVE_RTCD
CELT_SOURCES += $(CELT_SOURCES_X86_RTCD)
SILK_SOURCES += $(SILK_SOURCES_X86_RTCD)
if ENABLE_DEEP_PLC
LPCNET_SOURCES += $(DNN_SOURCES_X86_RTCD)
endif
endif
if HAVE_SSE
CELT_SOURCES += $(CELT_SOURCES_SSE)
endif
if HAVE_SSE2
CELT_SOURCES += $(CELT_SOURCES_SSE2)
if ENABLE_DEEP_PLC
LPCNET_SOURCES += $(DNN_SOURCES_SSE2)
endif
endif
if HAVE_SSE4_1
CELT_SOURCES += $(CELT_SOURCES_SSE4_1)
if ENABLE_DEEP_PLC
LPCNET_SOURCES += $(DNN_SOURCES_SSE4_1)
endif
endif
if HAVE_AVX2
SILK_SOURCES += $(SILK_SOURCES_AVX2)
CELT_SOURCES += $(CELT_SOURCES_AVX2)
if ENABLE_DEEP_PLC
LPCNET_SOURCES += $(DNN_SOURCES_AVX2)
endif
endif
endif
if CPU_ARM
if HAVE_RTCD
CELT_SOURCES += $(CELT_SOURCES_ARM_RTCD)
SILK_SOURCES += $(SILK_SOURCES_ARM_RTCD)
if ENABLE_DEEP_PLC
LPCNET_SOURCES += $(DNN_SOURCES_ARM_RTCD)
endif
endif
if ENABLE_DEEP_PLC
if HAVE_ARM_DOTPROD
LPCNET_SOURCES += $(DNN_SOURCES_DOTPROD)
endif
if HAVE_ARM_NEON_INTR
LPCNET_SOURCES += $(DNN_SOURCES_NEON)
endif
endif
if HAVE_ARM_NEON_INTR
CELT_SOURCES += $(CELT_SOURCES_ARM_NEON_INTR)
SILK_SOURCES += $(SILK_SOURCES_ARM_NEON_INTR)
endif
if HAVE_ARM_NE10
CELT_SOURCES += $(CELT_SOURCES_ARM_NE10)
endif
if OPUS_ARM_EXTERNAL_ASM
noinst_LTLIBRARIES = libarmasm.la
libarmasm_la_SOURCES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S)
BUILT_SOURCES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S) \
$(CELT_AM_SOURCES_ARM_ASM:.s.in=.s) \
$(CELT_AM_SOURCES_ARM_ASM:.s.in=-gnu.S)
endif
endif
CLEANFILES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S) \
$(CELT_AM_SOURCES_ARM_ASM:.s.in=-gnu.S)
include celt_headers.mk
include lpcnet_headers.mk
include silk_headers.mk
include opus_headers.mk
LPCNET_HEAD =
if ENABLE_DEEP_PLC
LPCNET_HEAD += $(DEEP_PLC_HEAD)
endif
if ENABLE_DRED
LPCNET_HEAD += $(DRED_HEAD)
endif
if ENABLE_OSCE
LPCNET_HEAD += $(OSCE_HEAD)
endif
if ENABLE_LOSSGEN
LPCNET_HEAD += $(LOSSGEN_HEAD)
endif
libopus_la_SOURCES = $(CELT_SOURCES) $(SILK_SOURCES) $(LPCNET_SOURCES) $(OPUS_SOURCES)
libopus_la_LDFLAGS = -no-undefined -version-info @OPUS_LT_CURRENT@:@OPUS_LT_REVISION@:@OPUS_LT_AGE@
libopus_la_LIBADD = $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
libopus_la_LIBADD += libarmasm.la
endif
pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h include/opus_projection.h
noinst_HEADERS = $(OPUS_HEAD) $(SILK_HEAD) $(CELT_HEAD) $(LPCNET_HEAD)
if EXTRA_PROGRAMS
noinst_PROGRAMS = celt/tests/test_unit_cwrs32 \
celt/tests/test_unit_dft \
celt/tests/test_unit_entropy \
celt/tests/test_unit_laplace \
celt/tests/test_unit_mathops \
celt/tests/test_unit_mdct \
celt/tests/test_unit_rotation \
celt/tests/test_unit_types \
opus_compare \
opus_demo \
repacketizer_demo \
silk/tests/test_unit_LPC_inv_pred_gain \
tests/test_opus_api \
tests/test_opus_decode \
tests/test_opus_dred \
tests/test_opus_encode \
tests/test_opus_extensions \
tests/test_opus_padding \
tests/test_opus_projection \
trivial_example
TESTS = celt/tests/test_unit_cwrs32 \
celt/tests/test_unit_dft \
celt/tests/test_unit_entropy \
celt/tests/test_unit_laplace \
celt/tests/test_unit_mathops \
celt/tests/test_unit_mdct \
celt/tests/test_unit_rotation \
celt/tests/test_unit_types \
silk/tests/test_unit_LPC_inv_pred_gain \
tests/test_opus_api \
tests/test_opus_decode \
tests/test_opus_encode \
tests/test_opus_extensions \
tests/test_opus_padding \
tests/test_opus_projection
opus_demo_SOURCES = src/opus_demo.c
if ENABLE_LOSSGEN
opus_demo_SOURCES += $(LOSSGEN_SOURCES)
endif
opus_demo_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
repacketizer_demo_SOURCES = src/repacketizer_demo.c
repacketizer_demo_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
opus_compare_SOURCES = src/opus_compare.c
opus_compare_LDADD = $(LIBM)
trivial_example_SOURCES = doc/trivial_example.c
trivial_example_LDADD = libopus.la $(LIBM)
tests_test_opus_api_SOURCES = tests/test_opus_api.c tests/test_opus_common.h
tests_test_opus_api_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
tests_test_opus_encode_SOURCES = tests/test_opus_encode.c tests/opus_encode_regressions.c tests/test_opus_common.h
tests_test_opus_encode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
tests_test_opus_decode_SOURCES = tests/test_opus_decode.c tests/test_opus_common.h
tests_test_opus_decode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
tests_test_opus_padding_SOURCES = tests/test_opus_padding.c tests/test_opus_common.h
tests_test_opus_padding_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
tests_test_opus_dred_SOURCES = tests/test_opus_dred.c tests/test_opus_common.h
tests_test_opus_dred_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
CELT_OBJ = $(CELT_SOURCES:.c=.lo)
SILK_OBJ = $(SILK_SOURCES:.c=.lo)
LPCNET_OBJ = $(LPCNET_SOURCES:.c=.lo)
OPUS_OBJ = $(OPUS_SOURCES:.c=.lo)
tests_test_opus_extensions_SOURCES = tests/test_opus_extensions.c tests/test_opus_common.h
tests_test_opus_extensions_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
tests_test_opus_extensions_LDADD += libarmasm.la
endif
tests_test_opus_projection_SOURCES = tests/test_opus_projection.c tests/test_opus_common.h
tests_test_opus_projection_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
tests_test_opus_projection_LDADD += libarmasm.la
endif
silk_tests_test_unit_LPC_inv_pred_gain_SOURCES = silk/tests/test_unit_LPC_inv_pred_gain.c
silk_tests_test_unit_LPC_inv_pred_gain_LDADD = $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
silk_tests_test_unit_LPC_inv_pred_gain_LDADD += libarmasm.la
endif
celt_tests_test_unit_cwrs32_SOURCES = celt/tests/test_unit_cwrs32.c
celt_tests_test_unit_cwrs32_LDADD = $(LIBM)
celt_tests_test_unit_dft_SOURCES = celt/tests/test_unit_dft.c
celt_tests_test_unit_dft_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_dft_LDADD += libarmasm.la
endif
celt_tests_test_unit_entropy_SOURCES = celt/tests/test_unit_entropy.c
celt_tests_test_unit_entropy_LDADD = $(LIBM)
celt_tests_test_unit_laplace_SOURCES = celt/tests/test_unit_laplace.c
celt_tests_test_unit_laplace_LDADD = $(LIBM)
celt_tests_test_unit_mathops_SOURCES = celt/tests/test_unit_mathops.c
celt_tests_test_unit_mathops_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_mathops_LDADD += libarmasm.la
endif
celt_tests_test_unit_mdct_SOURCES = celt/tests/test_unit_mdct.c
celt_tests_test_unit_mdct_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_mdct_LDADD += libarmasm.la
endif
celt_tests_test_unit_rotation_SOURCES = celt/tests/test_unit_rotation.c
celt_tests_test_unit_rotation_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_rotation_LDADD += libarmasm.la
endif
celt_tests_test_unit_types_SOURCES = celt/tests/test_unit_types.c
celt_tests_test_unit_types_LDADD = $(LIBM)
endif
if CUSTOM_MODES
pkginclude_HEADERS += include/opus_custom.h
if EXTRA_PROGRAMS
noinst_PROGRAMS += opus_custom_demo
opus_custom_demo_SOURCES = celt/opus_custom_demo.c
opus_custom_demo_LDADD = libopus.la $(LIBM)
endif
endif
if EXTRA_PROGRAMS
if ENABLE_DEEP_PLC
noinst_PROGRAMS += fargan_demo dump_data dump_weights_blob
fargan_demo_SOURCES = dnn/fargan_demo.c
fargan_demo_LDADD = $(LPCNET_OBJ) $(CELT_OBJ) $(LIBM)
dump_data_SOURCES = dnn/dump_data.c
dump_data_LDADD = $(LPCNET_OBJ) $(CELT_OBJ) $(LIBM)
dump_weights_blob_SOURCES = dnn/write_lpcnet_weights.c
dump_weights_blob_LDADD = $(LIBM)
dump_weights_blob_CFLAGS = $(AM_CFLAGS) -DDUMP_BINARY_WEIGHTS
endif
if ENABLE_DRED
TESTS += tests/test_opus_dred
endif
if ENABLE_LOSSGEN
noinst_PROGRAMS += lossgen_demo
lossgen_demo_SOURCES = dnn/lossgen_demo.c $(LOSSGEN_SOURCES)
lossgen_demo_LDADD = $(LIBM)
endif
endif
EXTRA_DIST = opus.pc.in \
opus-uninstalled.pc.in \
opus.m4 \
Makefile.mips \
Makefile.unix \
CMakeLists.txt \
cmake/CFeatureCheck.cmake \
cmake/OpusBuildtype.cmake \
cmake/OpusConfig.cmake \
cmake/OpusConfig.cmake.in \
cmake/OpusFunctions.cmake \
cmake/OpusPackageVersion.cmake \
cmake/OpusSources.cmake \
cmake/README.md \
cmake/RunTest.cmake \
cmake/config.h.cmake.in \
cmake/vla.c \
cmake/cpu_info_by_asm.c \
cmake/cpu_info_by_c.c \
meson/get-version.py \
meson/read-sources-list.py \
meson/README.md \
meson.build \
meson_options.txt \
include/meson.build \
celt/meson.build \
celt/tests/meson.build \
dnn/meson.build \
dnn/README.md \
silk/meson.build \
silk/tests/meson.build \
src/meson.build \
tests/meson.build \
doc/meson.build \
tests/run_vectors.sh \
celt/arm/arm2gnu.pl \
celt/arm/celt_pitch_xcorr_arm.s
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = opus.pc
m4datadir = $(datadir)/aclocal
m4data_DATA = opus.m4
# Targets to build and install just the library without the docs
opus check-opus install-opus: export NO_DOXYGEN = 1
opus: all
check-opus: check
install-opus: install
# Or just the docs
docs:
( cd doc && $(MAKE) $(AM_MAKEFLAGS) )
install-docs:
( cd doc && $(MAKE) $(AM_MAKEFLAGS) install )
# Or everything (by default)
all-local:
@[ -n "$(NO_DOXYGEN)" ] || ( cd doc && $(MAKE) $(AM_MAKEFLAGS) )
install-data-local:
@[ -n "$(NO_DOXYGEN)" ] || ( cd doc && $(MAKE) $(AM_MAKEFLAGS) install )
clean-local:
-( cd doc && $(MAKE) $(AM_MAKEFLAGS) clean )
uninstall-local:
( cd doc && $(MAKE) $(AM_MAKEFLAGS) uninstall )
# We check this every time make is run, with configure.ac being touched to
# trigger an update of the build system files if update_version changes the
# current PACKAGE_VERSION (or if package_version was modified manually by a
# user with either AUTO_UPDATE=no or no update_version script present - the
# latter being the normal case for tarball releases).
#
# We can't just add the package_version file to CONFIGURE_DEPENDENCIES since
# simply running autoconf will not actually regenerate configure for us when
# the content of that file changes (due to autoconf dependency checking not
# knowing about that without us creating yet another file for it to include).
#
# The MAKECMDGOALS check is a gnu-make'ism, but will degrade 'gracefully' for
# makes that don't support it. The only loss of functionality is not forcing
# an update of package_version for `make dist` if AUTO_UPDATE=no, but that is
# unlikely to be a real problem for any real user.
$(top_srcdir)/configure.ac: force
@case "$(MAKECMDGOALS)" in \
dist-hook) exit 0 ;; \
dist-* | dist | distcheck | distclean) _arg=release ;; \
esac; \
if ! $(top_srcdir)/update_version $$_arg 2> /dev/null; then \
if [ ! -e $(top_srcdir)/package_version ]; then \
echo 'PACKAGE_VERSION="unknown"' > $(top_srcdir)/package_version; \
fi; \
. $(top_srcdir)/package_version || exit 1; \
[ "$(PACKAGE_VERSION)" != "$$PACKAGE_VERSION" ] || exit 0; \
fi; \
touch $@
force:
# Create a minimal package_version file when make dist is run.
dist-hook:
echo 'PACKAGE_VERSION="$(PACKAGE_VERSION)"' > $(top_distdir)/package_version
.PHONY: opus check-opus install-opus docs install-docs
# automake doesn't do dependency tracking for asm files, that I can tell
$(CELT_SOURCES_ARM_ASM:%.s=%-gnu.S): celt/arm/armopts-gnu.S
$(CELT_SOURCES_ARM_ASM:%.s=%-gnu.S): $(top_srcdir)/celt/arm/arm2gnu.pl
# convert ARM asm to GNU as format
%-gnu.S: $(top_srcdir)/%.s
$(top_srcdir)/celt/arm/arm2gnu.pl @ARM2GNU_PARAMS@ < $< > $@
# For autoconf-modified sources (e.g., armopts.s)
%-gnu.S: %.s
$(top_srcdir)/celt/arm/arm2gnu.pl @ARM2GNU_PARAMS@ < $< > $@
OPT_UNIT_TEST_OBJ = $(celt_tests_test_unit_mathops_SOURCES:.c=.o) \
$(celt_tests_test_unit_rotation_SOURCES:.c=.o) \
$(celt_tests_test_unit_mdct_SOURCES:.c=.o) \
$(celt_tests_test_unit_dft_SOURCES:.c=.o) \
$(silk_tests_test_unit_LPC_inv_pred_gain_SOURCES:.c=.o)
if HAVE_SSE
SSE_OBJ = $(CELT_SOURCES_SSE:.c=.lo)
$(SSE_OBJ): CFLAGS += $(OPUS_X86_SSE_CFLAGS)
endif
if HAVE_SSE2
SSE2_OBJ = $(CELT_SOURCES_SSE2:.c=.lo) \
$(DNN_SOURCES_SSE2:.c=.lo)
$(SSE2_OBJ): CFLAGS += $(OPUS_X86_SSE2_CFLAGS)
endif
if HAVE_SSE4_1
SSE4_1_OBJ = $(CELT_SOURCES_SSE4_1:.c=.lo) \
$(DNN_SOURCES_SSE4_1:.c=.lo) \
$(SILK_SOURCES_SSE4_1:.c=.lo) \
$(SILK_SOURCES_FIXED_SSE4_1:.c=.lo)
$(SSE4_1_OBJ): CFLAGS += $(OPUS_X86_SSE4_1_CFLAGS)
endif
if HAVE_AVX2
AVX2_OBJ = $(CELT_SOURCES_AVX2:.c=.lo) \
$(SILK_SOURCES_AVX2:.c=.lo) \
$(SILK_SOURCES_FLOAT_AVX2:.c=.lo) \
$(DNN_SOURCES_AVX2:.c=.lo)
$(AVX2_OBJ): CFLAGS += $(OPUS_X86_AVX2_CFLAGS)
endif
if HAVE_ARM_NEON_INTR
ARM_NEON_INTR_OBJ = $(CELT_SOURCES_ARM_NEON_INTR:.c=.lo) \
$(SILK_SOURCES_ARM_NEON_INTR:.c=.lo) \
$(DNN_SOURCES_NEON:.c=.lo) \
$(SILK_SOURCES_FIXED_ARM_NEON_INTR:.c=.lo)
$(ARM_NEON_INTR_OBJ): CFLAGS += \
$(OPUS_ARM_NEON_INTR_CFLAGS) $(NE10_CFLAGS)
endif
if HAVE_ARM_DOTPROD
ARM_DOTPROD_OBJ = $(DNN_SOURCES_DOTPROD:.c=.lo)
$(ARM_DOTPROD_OBJ): CFLAGS += $(ARM_DOTPROD_INTR_CFLAGS)
endif

4853
local_plugins/opus/android/libs/opus/Makefile.in

File diff suppressed because it is too large

169
local_plugins/opus/android/libs/opus/Makefile.mips

@ -1,169 +0,0 @@
#################### COMPILE OPTIONS #######################
# Uncomment this for fixed-point build
FIXED_POINT=1
# It is strongly recommended to uncomment one of these
# VAR_ARRAYS: Use C99 variable-length arrays for stack allocation
# USE_ALLOCA: Use alloca() for stack allocation
# If none is defined, then the fallback is a non-threadsafe global array
CFLAGS := -DUSE_ALLOCA $(CFLAGS)
#CFLAGS := -DVAR_ARRAYS $(CFLAGS)
# These options affect performance
# HAVE_LRINTF: Use C99 intrinsics to speed up float-to-int conversion
CFLAGS := -DHAVE_LRINTF $(CFLAGS)
###################### END OF OPTIONS ######################
-include package_version
include silk_sources.mk
include celt_sources.mk
include opus_sources.mk
ifdef FIXED_POINT
SILK_SOURCES += $(SILK_SOURCES_FIXED)
else
SILK_SOURCES += $(SILK_SOURCES_FLOAT)
OPUS_SOURCES += $(OPUS_SOURCES_FLOAT)
endif
EXESUFFIX =
LIBPREFIX = lib
LIBSUFFIX = .a
OBJSUFFIX = .o
CC = $(TOOLCHAIN_PREFIX)cc$(TOOLCHAIN_SUFFIX)
AR = $(TOOLCHAIN_PREFIX)ar
RANLIB = $(TOOLCHAIN_PREFIX)ranlib
CP = $(TOOLCHAIN_PREFIX)cp
cppflags-from-defines = $(addprefix -D,$(1))
cppflags-from-includes = $(addprefix -I,$(1))
ldflags-from-ldlibdirs = $(addprefix -L,$(1))
ldlibs-from-libs = $(addprefix -l,$(1))
WARNINGS = -Wall -W -Wstrict-prototypes -Wextra -Wcast-align -Wnested-externs -Wshadow
CFLAGS += -mips32r2 -mno-mips16 -std=gnu99 -O2 -g $(WARNINGS) -DENABLE_ASSERTIONS -DMIPSr1_ASM -DOPUS_BUILD -mdspr2 -march=74kc -mtune=74kc -mmt -mgp32
CINCLUDES = include silk celt
ifdef FIXED_POINT
CFLAGS += -DFIXED_POINT=1 -DDISABLE_FLOAT_API
CINCLUDES += silk/fixed
else
CINCLUDES += silk/float
endif
LIBS = m
LDLIBDIRS = ./
CFLAGS += $(call cppflags-from-defines,$(CDEFINES))
CFLAGS += $(call cppflags-from-includes,$(CINCLUDES))
LDFLAGS += $(call ldflags-from-ldlibdirs,$(LDLIBDIRS))
LDLIBS += $(call ldlibs-from-libs,$(LIBS))
COMPILE.c.cmdline = $(CC) -c $(CFLAGS) -o $@ $<
LINK.o = $(CC) $(LDPREFLAGS) $(LDFLAGS)
LINK.o.cmdline = $(LINK.o) $^ $(LDLIBS) -o $@$(EXESUFFIX)
ARCHIVE.cmdline = $(AR) $(ARFLAGS) $@ $^ && $(RANLIB) $@
%$(OBJSUFFIX):%.c
$(COMPILE.c.cmdline)
%$(OBJSUFFIX):%.cpp
$(COMPILE.cpp.cmdline)
# Directives
# Variable definitions
LIB_NAME = opus
TARGET = $(LIBPREFIX)$(LIB_NAME)$(LIBSUFFIX)
SRCS_C = $(SILK_SOURCES) $(CELT_SOURCES) $(OPUS_SOURCES)
OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(SRCS_C))
OPUSDEMO_SRCS_C = src/opus_demo.c
OPUSDEMO_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSDEMO_SRCS_C))
TESTOPUSAPI_SRCS_C = tests/test_opus_api.c
TESTOPUSAPI_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSAPI_SRCS_C))
TESTOPUSDECODE_SRCS_C = tests/test_opus_decode.c
TESTOPUSDECODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSDECODE_SRCS_C))
TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
OPUSCOMPARE_SRCS_C = src/opus_compare.c
OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
# Rules
all: lib opus_demo opus_compare $(TESTS)
lib: $(TARGET)
check: all
for test in $(TESTS); do ./$$test; done
$(TARGET): $(OBJS)
$(ARCHIVE.cmdline)
opus_demo$(EXESUFFIX): $(OPUSDEMO_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_api$(EXESUFFIX): $(TESTOPUSAPI_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_decode$(EXESUFFIX): $(TESTOPUSDECODE_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
$(LINK.o.cmdline)
opus_compare$(EXESUFFIX): $(OPUSCOMPARE_OBJS)
$(LINK.o.cmdline)
celt/celt.o: CFLAGS += -DPACKAGE_VERSION='$(PACKAGE_VERSION)'
celt/celt.o: package_version
package_version: force
@if [ -x ./update_version ]; then \
./update_version || true; \
elif [ ! -e ./package_version ]; then \
echo 'PACKAGE_VERSION="unknown"' > ./package_version; \
fi
force:
clean:
rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
test_opus_padding$(EXESUFFIX)
$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
$(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
$(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
.PHONY: all lib clean force check

167
local_plugins/opus/android/libs/opus/Makefile.unix

@ -1,167 +0,0 @@
#################### COMPILE OPTIONS #######################
# Uncomment this for fixed-point build
#FIXED_POINT=1
# It is strongly recommended to uncomment one of these
# VAR_ARRAYS: Use C99 variable-length arrays for stack allocation
# USE_ALLOCA: Use alloca() for stack allocation
# If none is defined, then the fallback is a non-threadsafe global array
CFLAGS := -DUSE_ALLOCA $(CFLAGS)
#CFLAGS := -DVAR_ARRAYS $(CFLAGS)
# These options affect performance
# HAVE_LRINTF: Use C99 intrinsics to speed up float-to-int conversion
#CFLAGS := -DHAVE_LRINTF $(CFLAGS)
###################### END OF OPTIONS ######################
-include package_version
include silk_sources.mk
include celt_sources.mk
include opus_sources.mk
ifdef FIXED_POINT
SILK_SOURCES += $(SILK_SOURCES_FIXED)
else
SILK_SOURCES += $(SILK_SOURCES_FLOAT)
OPUS_SOURCES += $(OPUS_SOURCES_FLOAT)
endif
EXESUFFIX =
LIBPREFIX = lib
LIBSUFFIX = .a
OBJSUFFIX = .o
CC = $(TOOLCHAIN_PREFIX)cc$(TOOLCHAIN_SUFFIX)
AR = $(TOOLCHAIN_PREFIX)ar
RANLIB = $(TOOLCHAIN_PREFIX)ranlib
CP = $(TOOLCHAIN_PREFIX)cp
cppflags-from-defines = $(addprefix -D,$(1))
cppflags-from-includes = $(addprefix -I,$(1))
ldflags-from-ldlibdirs = $(addprefix -L,$(1))
ldlibs-from-libs = $(addprefix -l,$(1))
WARNINGS = -Wall -W -Wstrict-prototypes -Wextra -Wcast-align -Wnested-externs -Wshadow
CFLAGS += -O2 -g $(WARNINGS) -DOPUS_BUILD
CINCLUDES = include silk celt
ifdef FIXED_POINT
CFLAGS += -DFIXED_POINT=1 -DDISABLE_FLOAT_API
CINCLUDES += silk/fixed
else
CINCLUDES += silk/float
endif
LIBS = m
LDLIBDIRS = ./
CFLAGS += $(call cppflags-from-defines,$(CDEFINES))
CFLAGS += $(call cppflags-from-includes,$(CINCLUDES))
LDFLAGS += $(call ldflags-from-ldlibdirs,$(LDLIBDIRS))
LDLIBS += $(call ldlibs-from-libs,$(LIBS))
COMPILE.c.cmdline = $(CC) -c $(CFLAGS) -o $@ $<
LINK.o = $(CC) $(LDPREFLAGS) $(LDFLAGS)
LINK.o.cmdline = $(LINK.o) $^ $(LDLIBS) -o $@$(EXESUFFIX)
ARCHIVE.cmdline = $(AR) $(ARFLAGS) $@ $^ && $(RANLIB) $@
%$(OBJSUFFIX):%.c
$(COMPILE.c.cmdline)
%$(OBJSUFFIX):%.cpp
$(COMPILE.cpp.cmdline)
# Directives
# Variable definitions
LIB_NAME = opus
TARGET = $(LIBPREFIX)$(LIB_NAME)$(LIBSUFFIX)
SRCS_C = $(SILK_SOURCES) $(CELT_SOURCES) $(OPUS_SOURCES)
OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(SRCS_C))
OPUSDEMO_SRCS_C = src/opus_demo.c
OPUSDEMO_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSDEMO_SRCS_C))
TESTOPUSAPI_SRCS_C = tests/test_opus_api.c
TESTOPUSAPI_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSAPI_SRCS_C))
TESTOPUSDECODE_SRCS_C = tests/test_opus_decode.c
TESTOPUSDECODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSDECODE_SRCS_C))
TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
OPUSCOMPARE_SRCS_C = src/opus_compare.c
OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
# Rules
all: lib opus_demo opus_compare $(TESTS)
lib: $(TARGET)
check: all
for test in $(TESTS); do ./$$test; done
$(TARGET): $(OBJS)
$(ARCHIVE.cmdline)
opus_demo$(EXESUFFIX): $(OPUSDEMO_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_api$(EXESUFFIX): $(TESTOPUSAPI_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_decode$(EXESUFFIX): $(TESTOPUSDECODE_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
$(LINK.o.cmdline)
test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
$(LINK.o.cmdline)
opus_compare$(EXESUFFIX): $(OPUSCOMPARE_OBJS)
$(LINK.o.cmdline)
celt/celt.o: CFLAGS += -DPACKAGE_VERSION='$(PACKAGE_VERSION)'
celt/celt.o: package_version
package_version: force
@if [ -x ./update_version ]; then \
./update_version || true; \
elif [ ! -e ./package_version ]; then \
echo 'PACKAGE_VERSION="unknown"' > ./package_version; \
fi
force:
clean:
rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
test_opus_padding$(EXESUFFIX)
$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
$(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
$(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
.PHONY: all lib clean force check

0
local_plugins/opus/android/libs/opus/NEWS

189
local_plugins/opus/android/libs/opus/README

@ -1,189 +0,0 @@
== Opus audio codec ==
Opus is a codec for interactive speech and audio transmission over the Internet.
Opus can handle a wide range of interactive audio applications, including
Voice over IP, videoconferencing, in-game chat, and even remote live music
performances. It can scale from low bit-rate narrowband speech to very high
quality stereo music.
Opus, when coupled with an appropriate container format, is also suitable
for non-realtime stored-file applications such as music distribution, game
soundtracks, portable music players, jukeboxes, and other applications that
have historically used high latency formats such as MP3, AAC, or Vorbis.
Opus is specified by IETF RFC 6716:
https://tools.ietf.org/html/rfc6716
The Opus format and this implementation of it are subject to the royalty-
free patent and copyright licenses specified in the file COPYING.
This package implements a shared library for encoding and decoding raw Opus
bitstreams. Raw Opus bitstreams should be used over RTP according to
https://tools.ietf.org/html/rfc7587
The package also includes a number of test tools used for testing the
correct operation of the library. The bitstreams read/written by these
tools should not be used for Opus file distribution: They include
additional debugging data and cannot support seeking.
Opus stored in files should use the Ogg encapsulation for Opus which is
described at:
https://tools.ietf.org/html/rfc7845
An opus-tools package is available which provides encoding and decoding of
Ogg encapsulated Opus files and includes a number of useful features.
Opus-tools can be found at:
https://gitlab.xiph.org/xiph/opus-tools.git
or on the main Opus website:
https://opus-codec.org/
== Deep Learning and Opus ==
Lossy networks continue to be a challenge for real-time communications.
While the original implementation of Opus provides an excellent packet loss
concealment mechanism, the team has continued to advance the methodology used
to improve audio quality in challenge network environments.
In Opus 1.5, we added a deep learning based redundancy encoder that enhances
audio in lossy networks by embedding one second of recovery data in the padding
data of each packet. The underlying algorithm behind encoding and decoding the
recovery data is called the deep redundancy (DRED) algorithm. By leveraging
the padding data within the packet, Opus 1.5 is fully backward compatible with
prior revisions of Opus. Please see the README under the "dnn" subdirectory to
understand DRED.
DRED was developed by a team that Amazon Web Services initially sponsored,
who open-sourced the implementation as well as began the
standardization process at the IETF:
https://datatracker.ietf.org/doc/draft-ietf-mlcodec-opus-extension/
The license behind Opus or the intellectual property position of Opus does
not change with Opus 1.5.
== Compiling libopus ==
To build from a distribution tarball, you only need to do the following:
% ./configure
% make
To build from the git repository, the following steps are necessary:
0) Set up a development environment:
On an Ubuntu or Debian family Linux distribution:
% sudo apt-get install git autoconf automake libtool gcc make
On a Fedora/Redhat based Linux:
% sudo dnf install git autoconf automake libtool gcc make
Or for older Redhat/Centos Linux releases:
% sudo yum install git autoconf automake libtool gcc make
On Apple macOS, install Xcode and brew.sh, then in the Terminal enter:
% brew install autoconf automake libtool
1) Clone the repository:
% git clone https://gitlab.xiph.org/xiph/opus.git
% cd opus
2) Compiling the source
% ./autogen.sh
% ./configure
% make
On x86, it's a good idea to use a -march= option that allows the use of AVX2.
3) Install the codec libraries (optional)
% sudo make install
Once you have compiled the codec, there will be a opus_demo executable
in the top directory.
Usage: opus_demo [-e] <application> <sampling rate (Hz)> <channels (1/2)>
<bits per second> [options] <input> <output>
opus_demo -d <sampling rate (Hz)> <channels (1/2)> [options]
<input> <output>
mode: voip | audio | restricted-lowdelay
options:
-e : only runs the encoder (output the bit-stream)
-d : only runs the decoder (reads the bit-stream as input)
-cbr : enable constant bitrate; default: variable bitrate
-cvbr : enable constrained variable bitrate; default:
unconstrained
-bandwidth <NB|MB|WB|SWB|FB>
: audio bandwidth (from narrowband to fullband);
default: sampling rate
-framesize <2.5|5|10|20|40|60>
: frame size in ms; default: 20
-max_payload <bytes>
: maximum payload size in bytes, default: 1024
-complexity <comp>
: complexity, 0 (lowest) ... 10 (highest); default: 10
-inbandfec : enable SILK inband FEC
-forcemono : force mono encoding, even for stereo input
-dtx : enable SILK DTX
-loss <perc> : simulate packet loss, in percent (0-100); default: 0
input and output are little-endian signed 16-bit PCM files or opus
bitstreams with simple opus_demo proprietary framing.
== Testing ==
This package includes a collection of automated unit and system tests
which SHOULD be run after compiling the package especially the first
time it is run on a new platform.
To run the integrated tests:
% make check
There is also collection of standard test vectors which are not
included in this package for size reasons but can be obtained from:
https://opus-codec.org/docs/opus_testvectors-rfc8251.tar.gz
To run compare the code to these test vectors:
% curl -OL https://opus-codec.org/docs/opus_testvectors-rfc8251.tar.gz
% tar -zxf opus_testvectors-rfc8251.tar.gz
% ./tests/run_vectors.sh ./ opus_newvectors 48000
== Compiling libopus for Windows and alternative build systems ==
See cmake/README.md or meson/README.md.
== Portability notes ==
This implementation uses floating-point by default but can be compiled to
use only fixed-point arithmetic by setting --enable-fixed-point (if using
autoconf) or by defining the FIXED_POINT macro (if building manually).
The fixed point implementation has somewhat lower audio quality and is
slower on platforms with fast FPUs, it is normally only used in embedded
environments.
The implementation can be compiled with either a C89 or a C99 compiler.
While it does not rely on any _undefined behavior_ as defined by C89 or
C99, it relies on common _implementation-defined behavior_ for two's
complement architectures:
o Right shifts of negative values are consistent with two's
complement arithmetic, so that a>>b is equivalent to
floor(a/(2^b)),
o For conversion to a signed integer of N bits, the value is reduced
modulo 2^N to be within range of the type,
o The result of integer division of a negative value is truncated
towards zero, and
o The compiler provides a 64-bit integer type (a C99 requirement
which is supported by most C89 compilers).

1214
local_plugins/opus/android/libs/opus/aclocal.m4

File diff suppressed because it is too large

182
local_plugins/opus/android/libs/opus/celt/_kiss_fft_guts.h

@ -1,182 +0,0 @@
/*Copyright (c) 2003-2004, Mark Borgerding
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.*/
#ifndef KISS_FFT_GUTS_H
#define KISS_FFT_GUTS_H
#define MIN(a,b) ((a)<(b) ? (a):(b))
#define MAX(a,b) ((a)>(b) ? (a):(b))
/* kiss_fft.h
defines kiss_fft_scalar as either short or a float type
and defines
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
#include "kiss_fft.h"
/*
Explanation of macros dealing with complex math:
C_MUL(m,a,b) : m = a*b
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise
C_SUB( res, a,b) : res = a - b
C_SUBFROM( res , a) : res -= a
C_ADDTO( res , a) : res += a
* */
#ifdef FIXED_POINT
#include "arch.h"
#define SAMP_MAX 2147483647
#define TWID_MAX 32767
#define TRIG_UPSCALE 1
#define SAMP_MIN -SAMP_MAX
# define S_MUL(a,b) MULT16_32_Q15(b, a)
# define C_MUL(m,a,b) \
do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
(m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0)
# define C_MULC(m,a,b) \
do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
(m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0)
# define C_MULBYSCALAR( c, s ) \
do{ (c).r = S_MUL( (c).r , s ) ;\
(c).i = S_MUL( (c).i , s ) ; }while(0)
# define DIVSCALAR(x,k) \
(x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 )
# define C_FIXDIV(c,div) \
do { DIVSCALAR( (c).r , div); \
DIVSCALAR( (c).i , div); }while (0)
#define C_ADD( res, a,b)\
do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \
}while(0)
#define C_SUB( res, a,b)\
do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \
}while(0)
#define C_ADDTO( res , a)\
do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\
}while(0)
#define C_SUBFROM( res , a)\
do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \
}while(0)
#if defined(OPUS_ARM_INLINE_ASM)
#include "arm/kiss_fft_armv4.h"
#endif
#if defined(OPUS_ARM_INLINE_EDSP)
#include "arm/kiss_fft_armv5e.h"
#endif
#if defined(MIPSr1_ASM)
#include "mips/kiss_fft_mipsr1.h"
#endif
#else /* not FIXED_POINT*/
# define S_MUL(a,b) ( (a)*(b) )
#define C_MUL(m,a,b) \
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0)
#define C_MULC(m,a,b) \
do{ (m).r = (a).r*(b).r + (a).i*(b).i;\
(m).i = (a).i*(b).r - (a).r*(b).i; }while(0)
#define C_MUL4(m,a,b) C_MUL(m,a,b)
# define C_FIXDIV(c,div) /* NOOP */
# define C_MULBYSCALAR( c, s ) \
do{ (c).r *= (s);\
(c).i *= (s); }while(0)
#endif
#ifndef CHECK_OVERFLOW_OP
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */
#endif
#ifndef C_ADD
#define C_ADD( res, a,b)\
do { \
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
}while(0)
#define C_SUB( res, a,b)\
do { \
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
}while(0)
#define C_ADDTO( res , a)\
do { \
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
(res).r += (a).r; (res).i += (a).i;\
}while(0)
#define C_SUBFROM( res , a)\
do {\
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
(res).r -= (a).r; (res).i -= (a).i; \
}while(0)
#endif /* C_ADD defined */
#ifdef FIXED_POINT
/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase))))
# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/
# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase))
# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase))
# define HALF_OF(x) ((x)>>1)
#elif defined(USE_SIMD)
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )
# define HALF_OF(x) ((x)*_mm_set1_ps(.5f))
#else
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
# define HALF_OF(x) ((x)*.5f)
#endif
#define kf_cexp(x,phase) \
do{ \
(x)->r = KISS_FFT_COS(phase);\
(x)->i = KISS_FFT_SIN(phase);\
}while(0)
#define kf_cexp2(x,phase) \
do{ \
(x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\
(x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\
}while(0)
#endif /* KISS_FFT_GUTS_H */

291
local_plugins/opus/android/libs/opus/celt/arch.h

@ -1,291 +0,0 @@
/* Copyright (c) 2003-2008 Jean-Marc Valin
Copyright (c) 2007-2008 CSIRO
Copyright (c) 2007-2009 Xiph.Org Foundation
Written by Jean-Marc Valin */
/**
@file arch.h
@brief Various architecture definitions for CELT
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ARCH_H
#define ARCH_H
#include "opus_types.h"
#include "opus_defines.h"
# if !defined(__GNUC_PREREQ)
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
# define __GNUC_PREREQ(_maj,_min) \
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
# else
# define __GNUC_PREREQ(_maj,_min) 0
# endif
# endif
#if OPUS_GNUC_PREREQ(3, 0)
#define opus_likely(x) (__builtin_expect(!!(x), 1))
#define opus_unlikely(x) (__builtin_expect(!!(x), 0))
#else
#define opus_likely(x) (!!(x))
#define opus_unlikely(x) (!!(x))
#endif
#define CELT_SIG_SCALE 32768.f
#define CELT_FATAL(str) celt_fatal(str, __FILE__, __LINE__);
#if defined(ENABLE_ASSERTIONS) || defined(ENABLE_HARDENING)
#ifdef __GNUC__
__attribute__((noreturn))
#endif
void celt_fatal(const char *str, const char *file, int line);
#if defined(CELT_C) && !defined(OVERRIDE_celt_fatal)
#include <stdio.h>
#include <stdlib.h>
#ifdef __GNUC__
__attribute__((noreturn))
#endif
void celt_fatal(const char *str, const char *file, int line)
{
fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str);
#if defined(_MSC_VER)
_set_abort_behavior( 0, _WRITE_ABORT_MSG);
#endif
abort();
}
#endif
#define celt_assert(cond) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond);}}
#define celt_assert2(cond, message) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond "\n" message);}}
#define MUST_SUCCEED(call) celt_assert((call) == OPUS_OK)
#else
#define celt_assert(cond)
#define celt_assert2(cond, message)
#define MUST_SUCCEED(call) do {if((call) != OPUS_OK) {RESTORE_STACK; return OPUS_INTERNAL_ERROR;} } while (0)
#endif
#if defined(ENABLE_ASSERTIONS)
#define celt_sig_assert(cond) {if (!(cond)) {CELT_FATAL("signal assertion failed: " #cond);}}
#else
#define celt_sig_assert(cond)
#endif
#define IMUL32(a,b) ((a)*(b))
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */
#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */
#define UADD32(a,b) ((a)+(b))
#define USUB32(a,b) ((a)-(b))
/* Set this if opus_int64 is a native type of the CPU. */
/* Assume that all LP64 architectures have fast 64-bit types; also x86_64
(which can be ILP32 for x32) and Win64 (which is LLP64). */
#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64)
#define OPUS_FAST_INT64 1
#else
#define OPUS_FAST_INT64 0
#endif
#define PRINT_MIPS(file)
#ifdef FIXED_POINT
typedef opus_int16 opus_val16;
typedef opus_int32 opus_val32;
typedef opus_int64 opus_val64;
typedef opus_val32 celt_sig;
typedef opus_val16 celt_norm;
typedef opus_val32 celt_ener;
#define celt_isnan(x) 0
#define Q15ONE 32767
#define SIG_SHIFT 12
/* Safe saturation value for 32-bit signals. Should be less than
2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/
#define SIG_SAT (300000000)
#define NORM_SCALING 16384
#define DB_SHIFT 10
#define EPSILON 1
#define VERY_SMALL 0
#define VERY_LARGE16 ((opus_val16)32767)
#define Q15_ONE ((opus_val16)32767)
#define SCALEIN(a) (a)
#define SCALEOUT(a) (a)
#define ABS16(x) ((x) < 0 ? (-(x)) : (x))
#define ABS32(x) ((x) < 0 ? (-(x)) : (x))
static OPUS_INLINE opus_int16 SAT16(opus_int32 x) {
return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x;
}
#ifdef FIXED_DEBUG
#include "fixed_debug.h"
#else
#include "fixed_generic.h"
#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR
#include "arm/fixed_arm64.h"
#elif defined (OPUS_ARM_INLINE_EDSP)
#include "arm/fixed_armv5e.h"
#elif defined (OPUS_ARM_INLINE_ASM)
#include "arm/fixed_armv4.h"
#elif defined (BFIN_ASM)
#include "fixed_bfin.h"
#elif defined (TI_C5X_ASM)
#include "fixed_c5x.h"
#elif defined (TI_C6X_ASM)
#include "fixed_c6x.h"
#endif
#endif
#else /* FIXED_POINT */
typedef float opus_val16;
typedef float opus_val32;
typedef float opus_val64;
typedef float celt_sig;
typedef float celt_norm;
typedef float celt_ener;
#ifdef FLOAT_APPROX
/* This code should reliably detect NaN/inf even when -ffast-math is used.
Assumes IEEE 754 format. */
static OPUS_INLINE int celt_isnan(float x)
{
union {float f; opus_uint32 i;} in;
in.f = x;
return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0;
}
#else
#ifdef __FAST_MATH__
#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input
#endif
#define celt_isnan(x) ((x)!=(x))
#endif
#define Q15ONE 1.0f
#define NORM_SCALING 1.f
#define EPSILON 1e-15f
#define VERY_SMALL 1e-30f
#define VERY_LARGE16 1e15f
#define Q15_ONE ((opus_val16)1.f)
/* This appears to be the same speed as C99's fabsf() but it's more portable. */
#define ABS16(x) ((float)fabs(x))
#define ABS32(x) ((float)fabs(x))
#define QCONST16(x,bits) (x)
#define QCONST32(x,bits) (x)
#define NEG16(x) (-(x))
#define NEG32(x) (-(x))
#define NEG32_ovflw(x) (-(x))
#define EXTRACT16(x) (x)
#define EXTEND32(x) (x)
#define SHR16(a,shift) (a)
#define SHL16(a,shift) (a)
#define SHR32(a,shift) (a)
#define SHL32(a,shift) (a)
#define PSHR32(a,shift) (a)
#define VSHR32(a,shift) (a)
#define PSHR(a,shift) (a)
#define SHR(a,shift) (a)
#define SHL(a,shift) (a)
#define SATURATE(x,a) (x)
#define SATURATE16(x) (x)
#define ROUND16(a,shift) (a)
#define SROUND16(a,shift) (a)
#define HALF16(x) (.5f*(x))
#define HALF32(x) (.5f*(x))
#define ADD16(a,b) ((a)+(b))
#define SUB16(a,b) ((a)-(b))
#define ADD32(a,b) ((a)+(b))
#define SUB32(a,b) ((a)-(b))
#define ADD32_ovflw(a,b) ((a)+(b))
#define SUB32_ovflw(a,b) ((a)-(b))
#define MULT16_16_16(a,b) ((a)*(b))
#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b))
#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b))
#define MULT16_32_Q15(a,b) ((a)*(b))
#define MULT16_32_Q16(a,b) ((a)*(b))
#define MULT32_32_Q31(a,b) ((a)*(b))
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b))
#define MULT16_16_Q11_32(a,b) ((a)*(b))
#define MULT16_16_Q11(a,b) ((a)*(b))
#define MULT16_16_Q13(a,b) ((a)*(b))
#define MULT16_16_Q14(a,b) ((a)*(b))
#define MULT16_16_Q15(a,b) ((a)*(b))
#define MULT16_16_P15(a,b) ((a)*(b))
#define MULT16_16_P13(a,b) ((a)*(b))
#define MULT16_16_P14(a,b) ((a)*(b))
#define MULT16_32_P16(a,b) ((a)*(b))
#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b))
#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b))
#define SCALEIN(a) ((a)*CELT_SIG_SCALE)
#define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE))
#define SIG2WORD16(x) (x)
#endif /* !FIXED_POINT */
#ifndef GLOBAL_STACK_SIZE
#ifdef FIXED_POINT
#define GLOBAL_STACK_SIZE 120000
#else
#define GLOBAL_STACK_SIZE 120000
#endif
#endif
#endif /* ARCH_H */

353
local_plugins/opus/android/libs/opus/celt/arm/arm2gnu.pl

@ -1,353 +0,0 @@
#!/usr/bin/perl
# Copyright (C) 2002-2013 Xiph.org Foundation
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# - Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
my $bigend; # little/big endian
my $nxstack;
my $apple = 0;
my $symprefix = "";
$nxstack = 0;
eval 'exec /usr/local/bin/perl -S $0 ${1+"$@"}'
if $running_under_some_shell;
while ($ARGV[0] =~ /^-/) {
$_ = shift;
last if /^--$/;
if (/^-n$/) {
$nflag++;
next;
}
if (/^--apple$/) {
$apple = 1;
$symprefix = "_";
next;
}
die "I don't recognize this switch: $_\\n";
}
$printit++ unless $nflag;
$\ = "\n"; # automatically add newline on print
$n=0;
$thumb = 0; # ARM mode by default, not Thumb.
@proc_stack = ();
printf (" .syntax unified\n");
LINE:
while (<>) {
# For ADRLs we need to add a new line after the substituted one.
$addPadding = 0;
# First, we do not dare to touch *anything* inside double quotes, do we?
# Second, if you want a dollar character in the string,
# insert two of them -- that's how ARM C and assembler treat strings.
s/^([A-Za-z_]\w*)[ \t]+DCB[ \t]*\"/$1: .ascii \"/ && do { s/\$\$/\$/g; next };
s/\bDCB\b[ \t]*\"/.ascii \"/ && do { s/\$\$/\$/g; next };
s/^(\S+)\s+RN\s+(\S+)/$1 .req r$2/ && do { s/\$\$/\$/g; next };
# If there's nothing on a line but a comment, don't try to apply any further
# substitutions (this is a cheap hack to avoid mucking up the license header)
s/^([ \t]*);/$1@/ && do { s/\$\$/\$/g; next };
# If substituted -- leave immediately !
s/@/,:/;
s/;/@/;
while ( /@.*'/ ) {
s/(@.*)'/$1/g;
}
s/\{FALSE\}/0/g;
s/\{TRUE\}/1/g;
s/\{(\w\w\w\w+)\}/$1/g;
s/\bINCLUDE[ \t]*([^ \t\n]+)/.include \"$1\"/;
s/\bGET[ \t]*([^ \t\n]+)/.include \"${ my $x=$1; $x =~ s|\.s|-gnu.S|; \$x }\"/;
s/\bIMPORT\b/.extern/;
s/\bEXPORT\b\s*/.global $symprefix/;
s/^(\s+)\[/$1IF/;
s/^(\s+)\|/$1ELSE/;
s/^(\s+)\]/$1ENDIF/;
s/IF *:DEF:/ .ifdef/;
s/IF *:LNOT: *:DEF:/ .ifndef/;
s/ELSE/ .else/;
s/ENDIF/ .endif/;
if( /\bIF\b/ ) {
s/\bIF\b/ .if/;
s/=/==/;
}
if ( $n == 2) {
s/\$/\\/g;
}
if ($n == 1) {
s/\$//g;
s/label//g;
$n = 2;
}
if ( /MACRO/ ) {
s/MACRO *\n/.macro/;
$n=1;
}
if ( /\bMEND\b/ ) {
s/\bMEND\b/.endm/;
$n=0;
}
# ".rdata" doesn't work in 'as' version 2.13.2, as it is ".rodata" there.
#
if ( /\bAREA\b/ ) {
my $align;
$align = "2";
if ( /ALIGN=(\d+)/ ) {
$align = $1;
}
if ( /CODE/ ) {
$nxstack = 1;
}
s/^(.+)CODE(.+)READONLY(.*)/ .text/;
s/^(.+)DATA(.+)READONLY(.*)/ .section .rdata/;
s/^(.+)\|\|\.data\|\|(.+)/ .data/;
s/^(.+)\|\|\.bss\|\|(.+)/ .bss/;
s/$/; .p2align $align/;
# Enable NEON instructions but don't produce a binary that requires
# ARMv7. RVCT does not have equivalent directives, so we just do this
# for all CODE areas.
if ( /.text/ ) {
# Separating .arch, .fpu, etc., by semicolons does not work (gas
# thinks the semicolon is part of the arch name, even when there's
# whitespace separating them). Sadly this means our line numbers
# won't match the original source file (we could use the .line
# directive, which is documented to be obsolete, but then gdb will
# show the wrong line in the translated source file).
s/$/; .arch armv7-a\n .fpu neon\n .object_arch armv4t/ unless ($apple);
}
}
s/\|\|\.constdata\$(\d+)\|\|/.L_CONST$1/; # ||.constdata$3||
s/\|\|\.bss\$(\d+)\|\|/.L_BSS$1/; # ||.bss$2||
s/\|\|\.data\$(\d+)\|\|/.L_DATA$1/; # ||.data$2||
s/\|\|([a-zA-Z0-9_]+)\@([a-zA-Z0-9_]+)\|\|/@ $&/;
s/^(\s+)\%(\s)/ .space $1/;
s/\|(.+)\.(\d+)\|/\.$1_$2/; # |L80.123| -> .L80_123
s/\bCODE32\b/.code 32/ && do {$thumb = 0};
s/\bCODE16\b/.code 16/ && do {$thumb = 1};
if (/\bPROC\b/)
{
my $prefix;
my $proc;
/^([A-Za-z_\.]\w+)\b/;
$proc = $1;
$prefix = "";
if ($proc)
{
$prefix = $prefix.sprintf("\t.type\t%s, %%function", $proc) unless ($apple);
# Make sure we $prefix isn't empty here (for the $apple case).
# We handle mangling the label here, make sure it doesn't match
# the label handling below (if $prefix would be empty).
$prefix = $prefix."; ";
push(@proc_stack, $proc);
s/^[A-Za-z_\.]\w+/$symprefix$&:/;
}
$prefix = $prefix."\t.thumb_func; " if ($thumb);
s/\bPROC\b/@ $&/;
$_ = $prefix.$_;
}
s/^(\s*)(S|Q|SH|U|UQ|UH)ASX\b/$1$2ADDSUBX/;
s/^(\s*)(S|Q|SH|U|UQ|UH)SAX\b/$1$2SUBADDX/;
if (/\bENDP\b/)
{
my $proc;
s/\bENDP\b/@ $&/;
$proc = pop(@proc_stack);
$_ = "\t.size $proc, .-$proc".$_ if ($proc && !$apple);
}
s/\bSUBT\b/@ $&/;
s/\bDATA\b/@ $&/; # DATA directive is deprecated -- Asm guide, p.7-25
s/\bKEEP\b/@ $&/;
s/\bEXPORTAS\b/@ $&/;
s/\|\|(.)+\bEQU\b/@ $&/;
s/\|\|([\w\$]+)\|\|/$1/;
s/\bENTRY\b/@ $&/;
s/\bASSERT\b/@ $&/;
s/\bGBLL\b/@ $&/;
s/\bGBLA\b/@ $&/;
s/^\W+OPT\b/@ $&/;
s/:OR:/|/g;
s/:SHL:/<</g;
s/:SHR:/>>/g;
s/:AND:/&/g;
s/:LAND:/&&/g;
s/CPSR/cpsr/;
s/SPSR/spsr/;
s/ALIGN$/.balign 4/;
s/ALIGN\s+([0-9x]+)$/.balign $1/;
s/psr_cxsf/psr_all/;
s/LTORG/.ltorg/;
s/^([A-Za-z_]\w*)[ \t]+EQU/ .set $1,/;
s/^([A-Za-z_]\w*)[ \t]+SETL/ .set $1,/;
s/^([A-Za-z_]\w*)[ \t]+SETA/ .set $1,/;
s/^([A-Za-z_]\w*)[ \t]+\*/ .set $1,/;
# {PC} + 0xdeadfeed --> . + 0xdeadfeed
s/\{PC\} \+/ \. +/;
# Single hex constant on the line !
#
# >>> NOTE <<<
# Double-precision floats in gcc are always mixed-endian, which means
# bytes in two words are little-endian, but words are big-endian.
# So, 0x0000deadfeed0000 would be stored as 0x0000dead at low address
# and 0xfeed0000 at high address.
#
s/\bDCFD\b[ \t]+0x([a-fA-F0-9]{8})([a-fA-F0-9]{8})/.long 0x$1, 0x$2/;
# Only decimal constants on the line, no hex !
s/\bDCFD\b[ \t]+([0-9\.\-]+)/.double $1/;
# Single hex constant on the line !
# s/\bDCFS\b[ \t]+0x([a-f0-9]{8})([a-f0-9]{8})/.long 0x$1, 0x$2/;
# Only decimal constants on the line, no hex !
# s/\bDCFS\b[ \t]+([0-9\.\-]+)/.double $1/;
s/\bDCFS[ \t]+0x/.word 0x/;
s/\bDCFS\b/.float/;
s/^([A-Za-z_]\w*)[ \t]+DCD/$1 .word/;
s/\bDCD\b/.word/;
s/^([A-Za-z_]\w*)[ \t]+DCW/$1 .short/;
s/\bDCW\b/.short/;
s/^([A-Za-z_]\w*)[ \t]+DCB/$1 .byte/;
s/\bDCB\b/.byte/;
s/^([A-Za-z_]\w*)[ \t]+\%/.comm $1,/;
s/^[A-Za-z_\.]\w+/$&:/;
s/^(\d+)/$1:/;
s/\%(\d+)/$1b_or_f/;
s/\%[Bb](\d+)/$1b/;
s/\%[Ff](\d+)/$1f/;
s/\%[Ff][Tt](\d+)/$1f/;
s/&([\dA-Fa-f]+)/0x$1/;
if ( /\b2_[01]+\b/ ) {
s/\b2_([01]+)\b/conv$1&&&&/g;
while ( /[01][01][01][01]&&&&/ ) {
s/0000&&&&/&&&&0/g;
s/0001&&&&/&&&&1/g;
s/0010&&&&/&&&&2/g;
s/0011&&&&/&&&&3/g;
s/0100&&&&/&&&&4/g;
s/0101&&&&/&&&&5/g;
s/0110&&&&/&&&&6/g;
s/0111&&&&/&&&&7/g;
s/1000&&&&/&&&&8/g;
s/1001&&&&/&&&&9/g;
s/1010&&&&/&&&&A/g;
s/1011&&&&/&&&&B/g;
s/1100&&&&/&&&&C/g;
s/1101&&&&/&&&&D/g;
s/1110&&&&/&&&&E/g;
s/1111&&&&/&&&&F/g;
}
s/000&&&&/&&&&0/g;
s/001&&&&/&&&&1/g;
s/010&&&&/&&&&2/g;
s/011&&&&/&&&&3/g;
s/100&&&&/&&&&4/g;
s/101&&&&/&&&&5/g;
s/110&&&&/&&&&6/g;
s/111&&&&/&&&&7/g;
s/00&&&&/&&&&0/g;
s/01&&&&/&&&&1/g;
s/10&&&&/&&&&2/g;
s/11&&&&/&&&&3/g;
s/0&&&&/&&&&0/g;
s/1&&&&/&&&&1/g;
s/conv&&&&/0x/g;
}
if ( /commandline/)
{
if( /-bigend/)
{
$bigend=1;
}
}
if ( /\bDCDU\b/ )
{
my $cmd=$_;
my $value;
my $prefix;
my $w1;
my $w2;
my $w3;
my $w4;
s/\s+DCDU\b/@ $&/;
$cmd =~ /\bDCDU\b\s+0x(\d+)/;
$value = $1;
$value =~ /(\w\w)(\w\w)(\w\w)(\w\w)/;
$w1 = $1;
$w2 = $2;
$w3 = $3;
$w4 = $4;
if( $bigend ne "")
{
# big endian
$prefix = "\t.byte\t0x".$w1.";".
"\t.byte\t0x".$w2.";".
"\t.byte\t0x".$w3.";".
"\t.byte\t0x".$w4."; ";
}
else
{
# little endian
$prefix = "\t.byte\t0x".$w4.";".
"\t.byte\t0x".$w3.";".
"\t.byte\t0x".$w2.";".
"\t.byte\t0x".$w1."; ";
}
$_=$prefix.$_;
}
if ( /\badrl\b/i )
{
s/\badrl\s+(\w+)\s*,\s*(\w+)/ldr $1,=$2/i;
$addPadding = 1;
}
s/\bEND\b/@ END/;
} continue {
printf ("%s", $_) if $printit;
if ($addPadding != 0)
{
printf (" mov r0,r0\n");
$addPadding = 0;
}
}
#If we had a code section, mark that this object doesn't need an executable
# stack.
if ($nxstack && !$apple) {
printf (" .section\t.note.GNU-stack,\"\",\%\%progbits\n");
}

171
local_plugins/opus/android/libs/opus/celt/arm/arm_celt_map.c

@ -1,171 +0,0 @@
/* Copyright (c) 2010 Xiph.Org Foundation
* Copyright (c) 2013 Parrot */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "pitch.h"
#include "kiss_fft.h"
#include "mdct.h"
#if defined(OPUS_HAVE_RTCD)
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y, int N) = {
celt_inner_prod_c, /* ARMv4 */
celt_inner_prod_c, /* EDSP */
celt_inner_prod_c, /* Media */
celt_inner_prod_neon,/* NEON */
celt_inner_prod_neon /* DOTPROD */
};
void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02,
int N, opus_val32 *xy1, opus_val32 *xy2) = {
dual_inner_prod_c, /* ARMv4 */
dual_inner_prod_c, /* EDSP */
dual_inner_prod_c, /* Media */
dual_inner_prod_neon,/* NEON */
dual_inner_prod_neon /* DOTPROD */
};
# endif
# if defined(FIXED_POINT)
# if ((defined(OPUS_ARM_MAY_HAVE_NEON) && !defined(OPUS_ARM_PRESUME_NEON)) || \
(defined(OPUS_ARM_MAY_HAVE_MEDIA) && !defined(OPUS_ARM_PRESUME_MEDIA)) || \
(defined(OPUS_ARM_MAY_HAVE_EDSP) && !defined(OPUS_ARM_PRESUME_EDSP)))
opus_val32 (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *,
const opus_val16 *, opus_val32 *, int, int, int) = {
celt_pitch_xcorr_c, /* ARMv4 */
MAY_HAVE_EDSP(celt_pitch_xcorr), /* EDSP */
MAY_HAVE_MEDIA(celt_pitch_xcorr), /* Media */
MAY_HAVE_NEON(celt_pitch_xcorr), /* NEON */
MAY_HAVE_NEON(celt_pitch_xcorr) /* DOTPROD */
};
# endif
# else /* !FIXED_POINT */
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
void (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *,
const opus_val16 *, opus_val32 *, int, int, int) = {
celt_pitch_xcorr_c, /* ARMv4 */
celt_pitch_xcorr_c, /* EDSP */
celt_pitch_xcorr_c, /* Media */
celt_pitch_xcorr_float_neon, /* Neon */
celt_pitch_xcorr_float_neon /* DOTPROD */
};
# endif
# endif /* FIXED_POINT */
#if defined(FIXED_POINT) && defined(OPUS_HAVE_RTCD) && \
defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)
void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])(
const opus_val16 *x,
const opus_val16 *y,
opus_val32 sum[4],
int len
) = {
xcorr_kernel_c, /* ARMv4 */
xcorr_kernel_c, /* EDSP */
xcorr_kernel_c, /* Media */
xcorr_kernel_neon_fixed, /* Neon */
xcorr_kernel_neon_fixed /* DOTPROD */
};
#endif
# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
# if defined(HAVE_ARM_NE10)
# if defined(CUSTOM_MODES)
int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = {
opus_fft_alloc_arch_c, /* ARMv4 */
opus_fft_alloc_arch_c, /* EDSP */
opus_fft_alloc_arch_c, /* Media */
opus_fft_alloc_arm_neon, /* Neon with NE10 library support */
opus_fft_alloc_arm_neon /* DOTPROD with NE10 library support */
};
void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = {
opus_fft_free_arch_c, /* ARMv4 */
opus_fft_free_arch_c, /* EDSP */
opus_fft_free_arch_c, /* Media */
opus_fft_free_arm_neon, /* Neon with NE10 */
opus_fft_free_arm_neon /* DOTPROD with NE10 */
};
# endif /* CUSTOM_MODES */
void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
const kiss_fft_cpx *fin,
kiss_fft_cpx *fout) = {
opus_fft_c, /* ARMv4 */
opus_fft_c, /* EDSP */
opus_fft_c, /* Media */
opus_fft_neon, /* Neon with NE10 */
opus_fft_neon /* DOTPROD with NE10 */
};
void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
const kiss_fft_cpx *fin,
kiss_fft_cpx *fout) = {
opus_ifft_c, /* ARMv4 */
opus_ifft_c, /* EDSP */
opus_ifft_c, /* Media */
opus_ifft_neon, /* Neon with NE10 */
opus_ifft_neon /* DOTPROD with NE10 */
};
void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
kiss_fft_scalar *in,
kiss_fft_scalar * OPUS_RESTRICT out,
const opus_val16 *window,
int overlap, int shift,
int stride, int arch) = {
clt_mdct_forward_c, /* ARMv4 */
clt_mdct_forward_c, /* EDSP */
clt_mdct_forward_c, /* Media */
clt_mdct_forward_neon, /* Neon with NE10 */
clt_mdct_forward_neon /* DOTPROD with NE10 */
};
void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
kiss_fft_scalar *in,
kiss_fft_scalar * OPUS_RESTRICT out,
const opus_val16 *window,
int overlap, int shift,
int stride, int arch) = {
clt_mdct_backward_c, /* ARMv4 */
clt_mdct_backward_c, /* EDSP */
clt_mdct_backward_c, /* Media */
clt_mdct_backward_neon, /* Neon with NE10 */
clt_mdct_backward_neon /* DOTPROD with NE10 */
};
# endif /* HAVE_ARM_NE10 */
# endif /* OPUS_ARM_MAY_HAVE_NEON_INTR */
#endif /* OPUS_HAVE_RTCD */

291
local_plugins/opus/android/libs/opus/celt/arm/armcpu.c

@ -1,291 +0,0 @@
/* Copyright (c) 2010 Xiph.Org Foundation
* Copyright (c) 2013 Parrot */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Original code from libtheora modified to suit to Opus */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef OPUS_HAVE_RTCD
#include "armcpu.h"
#include "cpu_support.h"
#include "os_support.h"
#include "opus_types.h"
#include "arch.h"
#define OPUS_CPU_ARM_V4_FLAG (1<<OPUS_ARCH_ARM_V4)
#define OPUS_CPU_ARM_EDSP_FLAG (1<<OPUS_ARCH_ARM_EDSP)
#define OPUS_CPU_ARM_MEDIA_FLAG (1<<OPUS_ARCH_ARM_MEDIA)
#define OPUS_CPU_ARM_NEON_FLAG (1<<OPUS_ARCH_ARM_NEON)
#define OPUS_CPU_ARM_DOTPROD_FLAG (1<<OPUS_ARCH_ARM_DOTPROD)
#if defined(_MSC_VER)
/*For GetExceptionCode() and EXCEPTION_ILLEGAL_INSTRUCTION.*/
# define WIN32_LEAN_AND_MEAN
# define WIN32_EXTRA_LEAN
# include <windows.h>
static OPUS_INLINE opus_uint32 opus_cpu_capabilities(void){
opus_uint32 flags;
flags=0;
/* MSVC has no OPUS_INLINE __asm support for ARM, but it does let you __emit
* instructions via their assembled hex code.
* All of these instructions should be essentially nops. */
# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
__try{
/*PLD [r13]*/
__emit(0xF5DDF000);
flags|=OPUS_CPU_ARM_EDSP_FLAG;
}
__except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){
/*Ignore exception.*/
}
# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
__try{
/*SHADD8 r3,r3,r3*/
__emit(0xE6333F93);
flags|=OPUS_CPU_ARM_MEDIA_FLAG;
}
__except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){
/*Ignore exception.*/
}
# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
__try{
/*VORR q0,q0,q0*/
__emit(0xF2200150);
flags|=OPUS_CPU_ARM_NEON_FLAG;
}
__except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){
/*Ignore exception.*/
}
# endif
# endif
# endif
return flags;
}
#elif defined(__linux__)
/* Linux based */
#include <stdio.h>
static opus_uint32 opus_cpu_capabilities(void)
{
opus_uint32 flags = 0;
FILE *cpuinfo;
/* Reading /proc/self/auxv would be easier, but that doesn't work reliably on
* Android */
cpuinfo = fopen("/proc/cpuinfo", "r");
if(cpuinfo != NULL)
{
/* 512 should be enough for anybody (it's even enough for all the flags that
* x86 has accumulated... so far). */
char buf[512];
while(fgets(buf, 512, cpuinfo) != NULL)
{
# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
/* Search for edsp and neon flag */
if(memcmp(buf, "Features", 8) == 0)
{
char *p;
p = strstr(buf, " edsp");
if(p != NULL && (p[5] == ' ' || p[5] == '\n'))
flags |= OPUS_CPU_ARM_EDSP_FLAG;
# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
p = strstr(buf, " neon");
if(p != NULL && (p[5] == ' ' || p[5] == '\n'))
flags |= OPUS_CPU_ARM_NEON_FLAG;
p = strstr(buf, " asimd");
if(p != NULL && (p[6] == ' ' || p[6] == '\n'))
flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG;
# endif
# if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
p = strstr(buf, " asimddp");
if(p != NULL && (p[8] == ' ' || p[8] == '\n'))
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
# endif
}
# endif
# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
/* Search for media capabilities (>= ARMv6) */
if(memcmp(buf, "CPU architecture:", 17) == 0)
{
int version;
version = atoi(buf+17);
if(version >= 6)
flags |= OPUS_CPU_ARM_MEDIA_FLAG;
}
# endif
}
#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
# if defined(OPUS_ARM_PRESUME_DOTPROD)
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
# endif
#endif
fclose(cpuinfo);
}
return flags;
}
#elif defined(__APPLE__)
#include <sys/types.h>
#include <sys/sysctl.h>
static opus_uint32 opus_cpu_capabilities(void)
{
opus_uint32 flags = 0;
#if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
size_t size = sizeof(uint32_t);
uint32_t value = 0;
if (!sysctlbyname("hw.optional.arm.FEAT_DotProd", &value, &size, NULL, 0) && value)
{
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
}
#endif
#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
# if defined(OPUS_ARM_PRESUME_DOTPROD)
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
# endif
#endif
return flags;
}
#elif defined(__FreeBSD__)
#include <sys/auxv.h>
static opus_uint32 opus_cpu_capabilities(void)
{
long hwcap = 0;
opus_uint32 flags = 0;
# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
/* FreeBSD requires armv6+, which always supports media instructions */
flags |= OPUS_CPU_ARM_MEDIA_FLAG;
# endif
elf_aux_info(AT_HWCAP, &hwcap, sizeof hwcap);
# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \
|| defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
# ifdef HWCAP_EDSP
if (hwcap & HWCAP_EDSP)
flags |= OPUS_CPU_ARM_EDSP_FLAG;
# endif
# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
# ifdef HWCAP_NEON
if (hwcap & HWCAP_NEON)
flags |= OPUS_CPU_ARM_NEON_FLAG;
# elif defined(HWCAP_ASIMD)
if (hwcap & HWCAP_ASIMD)
flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG;
# endif
# endif
# if defined(OPUS_ARM_MAY_HAVE_DOTPROD) && defined(HWCAP_ASIMDDP)
if (hwcap & HWCAP_ASIMDDP)
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
# endif
# endif
#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
# if defined(OPUS_ARM_PRESUME_DOTPROD)
flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
# endif
#endif
return (flags);
}
#else
/* The feature registers which can tell us what the processor supports are
* accessible in priveleged modes only, so we can't have a general user-space
* detection method like on x86.*/
# error "Configured to use ARM asm but no CPU detection method available for " \
"your platform. Reconfigure with --disable-rtcd (or send patches)."
#endif
static int opus_select_arch_impl(void)
{
opus_uint32 flags = opus_cpu_capabilities();
int arch = 0;
if(!(flags & OPUS_CPU_ARM_EDSP_FLAG)) {
/* Asserts ensure arch values are sequential */
celt_assert(arch == OPUS_ARCH_ARM_V4);
return arch;
}
arch++;
if(!(flags & OPUS_CPU_ARM_MEDIA_FLAG)) {
celt_assert(arch == OPUS_ARCH_ARM_EDSP);
return arch;
}
arch++;
if(!(flags & OPUS_CPU_ARM_NEON_FLAG)) {
celt_assert(arch == OPUS_ARCH_ARM_MEDIA);
return arch;
}
arch++;
if(!(flags & OPUS_CPU_ARM_DOTPROD_FLAG)) {
celt_assert(arch == OPUS_ARCH_ARM_NEON);
return arch;
}
arch++;
celt_assert(arch == OPUS_ARCH_ARM_DOTPROD);
return arch;
}
int opus_select_arch(void) {
int arch = opus_select_arch_impl();
#ifdef FUZZING
arch = rand()%(arch+1);
#endif
return arch;
}
#endif

90
local_plugins/opus/android/libs/opus/celt/arm/armcpu.h

@ -1,90 +0,0 @@
/* Copyright (c) 2010 Xiph.Org Foundation
* Copyright (c) 2013 Parrot */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !defined(ARMCPU_H)
# define ARMCPU_H
# if defined(OPUS_ARM_MAY_HAVE_EDSP)
# define MAY_HAVE_EDSP(name) name ## _edsp
# else
# define MAY_HAVE_EDSP(name) name ## _c
# endif
# if defined(OPUS_ARM_MAY_HAVE_MEDIA)
# define MAY_HAVE_MEDIA(name) name ## _media
# else
# define MAY_HAVE_MEDIA(name) MAY_HAVE_EDSP(name)
# endif
# if defined(OPUS_ARM_MAY_HAVE_NEON)
# define MAY_HAVE_NEON(name) name ## _neon
# else
# define MAY_HAVE_NEON(name) MAY_HAVE_MEDIA(name)
# endif
# if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
# define MAY_HAVE_DOTPROD(name) name ## _dotprod
# else
# define MAY_HAVE_DOTPROD(name) MAY_HAVE_NEON(name)
# endif
# if defined(OPUS_ARM_PRESUME_EDSP)
# define PRESUME_EDSP(name) name ## _edsp
# else
# define PRESUME_EDSP(name) name ## _c
# endif
# if defined(OPUS_ARM_PRESUME_MEDIA)
# define PRESUME_MEDIA(name) name ## _media
# else
# define PRESUME_MEDIA(name) PRESUME_EDSP(name)
# endif
# if defined(OPUS_ARM_PRESUME_NEON)
# define PRESUME_NEON(name) name ## _neon
# else
# define PRESUME_NEON(name) PRESUME_MEDIA(name)
# endif
# if defined(OPUS_ARM_PRESUME_DOTPROD)
# define PRESUME_DOTPROD(name) name ## _dotprod
# else
# define PRESUME_DOTPROD(name) PRESUME_NEON(name)
# endif
# if defined(OPUS_HAVE_RTCD)
int opus_select_arch(void);
#define OPUS_ARCH_ARM_V4 (0)
#define OPUS_ARCH_ARM_EDSP (1)
#define OPUS_ARCH_ARM_MEDIA (2)
#define OPUS_ARCH_ARM_NEON (3)
#define OPUS_ARCH_ARM_DOTPROD (4)
# endif
#endif

37
local_plugins/opus/android/libs/opus/celt/arm/armopts.s.in

@ -1,37 +0,0 @@
/* Copyright (C) 2013 Mozilla Corporation */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
; Set the following to 1 if we have EDSP instructions
; (LDRD/STRD, etc., ARMv5E and later).
OPUS_ARM_MAY_HAVE_EDSP * @OPUS_ARM_MAY_HAVE_EDSP@
; Set the following to 1 if we have ARMv6 media instructions.
OPUS_ARM_MAY_HAVE_MEDIA * @OPUS_ARM_MAY_HAVE_MEDIA@
; Set the following to 1 if we have NEON (some ARMv7)
OPUS_ARM_MAY_HAVE_NEON * @OPUS_ARM_MAY_HAVE_NEON@
END

173
local_plugins/opus/android/libs/opus/celt/arm/celt_fft_ne10.c

@ -1,173 +0,0 @@
/* Copyright (c) 2015 Xiph.Org Foundation
Written by Viswanath Puttagunta */
/**
@file celt_fft_ne10.c
@brief ARM Neon optimizations for fft using NE10 library
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SKIP_CONFIG_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#endif
#include <NE10_dsp.h>
#include "os_support.h"
#include "kiss_fft.h"
#include "stack_alloc.h"
#if !defined(FIXED_POINT)
# define NE10_FFT_ALLOC_C2C_TYPE_NEON ne10_fft_alloc_c2c_float32_neon
# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_float32_t
# define NE10_FFT_STATE_TYPE_T ne10_fft_state_float32_t
# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_float32
# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_float32_t
# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_float32_neon
#else
# define NE10_FFT_ALLOC_C2C_TYPE_NEON(nfft) ne10_fft_alloc_c2c_int32_neon(nfft)
# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_int32_t
# define NE10_FFT_STATE_TYPE_T ne10_fft_state_int32_t
# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32
# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32
# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_int32_t
# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_int32_neon
#endif
#if defined(CUSTOM_MODES)
/* nfft lengths in NE10 that support scaled fft */
# define NE10_FFTSCALED_SUPPORT_MAX 4
static const int ne10_fft_scaled_support[NE10_FFTSCALED_SUPPORT_MAX] = {
480, 240, 120, 60
};
int opus_fft_alloc_arm_neon(kiss_fft_state *st)
{
int i;
size_t memneeded = sizeof(struct arch_fft_state);
st->arch_fft = (arch_fft_state *)opus_alloc(memneeded);
if (!st->arch_fft)
return -1;
for (i = 0; i < NE10_FFTSCALED_SUPPORT_MAX; i++) {
if(st->nfft == ne10_fft_scaled_support[i])
break;
}
if (i == NE10_FFTSCALED_SUPPORT_MAX) {
/* This nfft length (scaled fft) is not supported in NE10 */
st->arch_fft->is_supported = 0;
st->arch_fft->priv = NULL;
}
else {
st->arch_fft->is_supported = 1;
st->arch_fft->priv = (void *)NE10_FFT_ALLOC_C2C_TYPE_NEON(st->nfft);
if (st->arch_fft->priv == NULL) {
return -1;
}
}
return 0;
}
void opus_fft_free_arm_neon(kiss_fft_state *st)
{
NE10_FFT_CFG_TYPE_T cfg;
if (!st->arch_fft)
return;
cfg = (NE10_FFT_CFG_TYPE_T)st->arch_fft->priv;
if (cfg)
NE10_FFT_DESTROY_C2C_TYPE(cfg);
opus_free(st->arch_fft);
}
#endif
void opus_fft_neon(const kiss_fft_state *st,
const kiss_fft_cpx *fin,
kiss_fft_cpx *fout)
{
NE10_FFT_STATE_TYPE_T state;
NE10_FFT_CFG_TYPE_T cfg = &state;
VARDECL(NE10_FFT_CPX_TYPE_T, buffer);
SAVE_STACK;
ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T);
if (!st->arch_fft->is_supported) {
/* This nfft length (scaled fft) not supported in NE10 */
opus_fft_c(st, fin, fout);
}
else {
memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T));
state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0];
#if !defined(FIXED_POINT)
state.is_forward_scaled = 1;
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
(NE10_FFT_CPX_TYPE_T *)fin,
cfg, 0);
#else
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
(NE10_FFT_CPX_TYPE_T *)fin,
cfg, 0, 1);
#endif
}
RESTORE_STACK;
}
void opus_ifft_neon(const kiss_fft_state *st,
const kiss_fft_cpx *fin,
kiss_fft_cpx *fout)
{
NE10_FFT_STATE_TYPE_T state;
NE10_FFT_CFG_TYPE_T cfg = &state;
VARDECL(NE10_FFT_CPX_TYPE_T, buffer);
SAVE_STACK;
ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T);
if (!st->arch_fft->is_supported) {
/* This nfft length (scaled fft) not supported in NE10 */
opus_ifft_c(st, fin, fout);
}
else {
memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T));
state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0];
#if !defined(FIXED_POINT)
state.is_backward_scaled = 0;
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
(NE10_FFT_CPX_TYPE_T *)fin,
cfg, 1);
#else
NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout,
(NE10_FFT_CPX_TYPE_T *)fin,
cfg, 1, 0);
#endif
}
RESTORE_STACK;
}

258
local_plugins/opus/android/libs/opus/celt/arm/celt_mdct_ne10.c

@ -1,258 +0,0 @@
/* Copyright (c) 2015 Xiph.Org Foundation
Written by Viswanath Puttagunta */
/**
@file celt_mdct_ne10.c
@brief ARM Neon optimizations for mdct using NE10 library
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SKIP_CONFIG_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#endif
#include "kiss_fft.h"
#include "_kiss_fft_guts.h"
#include "mdct.h"
#include "stack_alloc.h"
void clt_mdct_forward_neon(const mdct_lookup *l,
kiss_fft_scalar *in,
kiss_fft_scalar * OPUS_RESTRICT out,
const opus_val16 *window,
int overlap, int shift, int stride, int arch)
{
int i;
int N, N2, N4;
VARDECL(kiss_fft_scalar, f);
VARDECL(kiss_fft_cpx, f2);
const kiss_fft_state *st = l->kfft[shift];
const kiss_twiddle_scalar *trig;
SAVE_STACK;
N = l->n;
trig = l->trig;
for (i=0;i<shift;i++)
{
N >>= 1;
trig += N;
}
N2 = N>>1;
N4 = N>>2;
ALLOC(f, N2, kiss_fft_scalar);
ALLOC(f2, N4, kiss_fft_cpx);
/* Consider the input to be composed of four blocks: [a, b, c, d] */
/* Window, shuffle, fold */
{
/* Temp pointers to make it really clear to the compiler what we're doing */
const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1);
const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1);
kiss_fft_scalar * OPUS_RESTRICT yp = f;
const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1);
const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1;
for(i=0;i<((overlap+3)>>2);i++)
{
/* Real part arranged as -d-cR, Imag part arranged as -b+aR*/
*yp++ = MULT16_32_Q15(*wp2, xp1[N2]) + MULT16_32_Q15(*wp1,*xp2);
*yp++ = MULT16_32_Q15(*wp1, *xp1) - MULT16_32_Q15(*wp2, xp2[-N2]);
xp1+=2;
xp2-=2;
wp1+=2;
wp2-=2;
}
wp1 = window;
wp2 = window+overlap-1;
for(;i<N4-((overlap+3)>>2);i++)
{
/* Real part arranged as a-bR, Imag part arranged as -c-dR */
*yp++ = *xp2;
*yp++ = *xp1;
xp1+=2;
xp2-=2;
}
for(;i<N4;i++)
{
/* Real part arranged as a-bR, Imag part arranged as -c-dR */
*yp++ = -MULT16_32_Q15(*wp1, xp1[-N2]) + MULT16_32_Q15(*wp2, *xp2);
*yp++ = MULT16_32_Q15(*wp2, *xp1) + MULT16_32_Q15(*wp1, xp2[N2]);
xp1+=2;
xp2-=2;
wp1+=2;
wp2-=2;
}
}
/* Pre-rotation */
{
kiss_fft_scalar * OPUS_RESTRICT yp = f;
const kiss_twiddle_scalar *t = &trig[0];
for(i=0;i<N4;i++)
{
kiss_fft_cpx yc;
kiss_twiddle_scalar t0, t1;
kiss_fft_scalar re, im, yr, yi;
t0 = t[i];
t1 = t[N4+i];
re = *yp++;
im = *yp++;
yr = S_MUL(re,t0) - S_MUL(im,t1);
yi = S_MUL(im,t0) + S_MUL(re,t1);
yc.r = yr;
yc.i = yi;
f2[i] = yc;
}
}
opus_fft(st, f2, (kiss_fft_cpx *)f, arch);
/* Post-rotate */
{
/* Temp pointers to make it really clear to the compiler what we're doing */
const kiss_fft_cpx * OPUS_RESTRICT fp = (kiss_fft_cpx *)f;
kiss_fft_scalar * OPUS_RESTRICT yp1 = out;
kiss_fft_scalar * OPUS_RESTRICT yp2 = out+stride*(N2-1);
const kiss_twiddle_scalar *t = &trig[0];
/* Temp pointers to make it really clear to the compiler what we're doing */
for(i=0;i<N4;i++)
{
kiss_fft_scalar yr, yi;
yr = S_MUL(fp->i,t[N4+i]) - S_MUL(fp->r,t[i]);
yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]);
*yp1 = yr;
*yp2 = yi;
fp++;
yp1 += 2*stride;
yp2 -= 2*stride;
}
}
RESTORE_STACK;
}
void clt_mdct_backward_neon(const mdct_lookup *l,
kiss_fft_scalar *in,
kiss_fft_scalar * OPUS_RESTRICT out,
const opus_val16 * OPUS_RESTRICT window,
int overlap, int shift, int stride, int arch)
{
int i;
int N, N2, N4;
VARDECL(kiss_fft_scalar, f);
const kiss_twiddle_scalar *trig;
const kiss_fft_state *st = l->kfft[shift];
N = l->n;
trig = l->trig;
for (i=0;i<shift;i++)
{
N >>= 1;
trig += N;
}
N2 = N>>1;
N4 = N>>2;
ALLOC(f, N2, kiss_fft_scalar);
/* Pre-rotate */
{
/* Temp pointers to make it really clear to the compiler what we're doing */
const kiss_fft_scalar * OPUS_RESTRICT xp1 = in;
const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1);
kiss_fft_scalar * OPUS_RESTRICT yp = f;
const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0];
for(i=0;i<N4;i++)
{
kiss_fft_scalar yr, yi;
yr = S_MUL(*xp2, t[i]) + S_MUL(*xp1, t[N4+i]);
yi = S_MUL(*xp1, t[i]) - S_MUL(*xp2, t[N4+i]);
yp[2*i] = yr;
yp[2*i+1] = yi;
xp1+=2*stride;
xp2-=2*stride;
}
}
opus_ifft(st, (kiss_fft_cpx *)f, (kiss_fft_cpx*)(out+(overlap>>1)), arch);
/* Post-rotate and de-shuffle from both ends of the buffer at once to make
it in-place. */
{
kiss_fft_scalar * yp0 = out+(overlap>>1);
kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2;
const kiss_twiddle_scalar *t = &trig[0];
/* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the
middle pair will be computed twice. */
for(i=0;i<(N4+1)>>1;i++)
{
kiss_fft_scalar re, im, yr, yi;
kiss_twiddle_scalar t0, t1;
re = yp0[0];
im = yp0[1];
t0 = t[i];
t1 = t[N4+i];
/* We'd scale up by 2 here, but instead it's done when mixing the windows */
yr = S_MUL(re,t0) + S_MUL(im,t1);
yi = S_MUL(re,t1) - S_MUL(im,t0);
re = yp1[0];
im = yp1[1];
yp0[0] = yr;
yp1[1] = yi;
t0 = t[(N4-i-1)];
t1 = t[(N2-i-1)];
/* We'd scale up by 2 here, but instead it's done when mixing the windows */
yr = S_MUL(re,t0) + S_MUL(im,t1);
yi = S_MUL(re,t1) - S_MUL(im,t0);
yp1[0] = yr;
yp0[1] = yi;
yp0 += 2;
yp1 -= 2;
}
}
/* Mirror on both sides for TDAC */
{
kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1;
kiss_fft_scalar * OPUS_RESTRICT yp1 = out;
const opus_val16 * OPUS_RESTRICT wp1 = window;
const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1;
for(i = 0; i < overlap/2; i++)
{
kiss_fft_scalar x1, x2;
x1 = *xp1;
x2 = *yp1;
*yp1++ = MULT16_32_Q15(*wp2, x2) - MULT16_32_Q15(*wp1, x1);
*xp1-- = MULT16_32_Q15(*wp1, x2) + MULT16_32_Q15(*wp2, x1);
wp1++;
wp2--;
}
}
RESTORE_STACK;
}

272
local_plugins/opus/android/libs/opus/celt/arm/celt_neon_intr.c

@ -1,272 +0,0 @@
/* Copyright (c) 2014-2015 Xiph.Org Foundation
Written by Viswanath Puttagunta */
/**
@file celt_neon_intr.c
@brief ARM Neon Intrinsic optimizations for celt
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <arm_neon.h>
#include "../pitch.h"
#if defined(FIXED_POINT)
#include <string.h>
void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len)
{
int j;
int32x4_t a = vld1q_s32(sum);
/* Load y[0...3] */
/* This requires len>0 to always be valid (which we assert in the C code). */
int16x4_t y0 = vld1_s16(y);
y += 4;
/* This loop loads one y value more than we actually need.
Therefore we have to stop as soon as there are 8 or fewer samples left
(instead of 7), to avoid reading past the end of the array. */
for (j = 0; j + 8 < len; j += 8)
{
/* Load x[0...7] */
int16x8_t xx = vld1q_s16(x);
int16x4_t x0 = vget_low_s16(xx);
int16x4_t x4 = vget_high_s16(xx);
/* Load y[4...11] */
int16x8_t yy = vld1q_s16(y);
int16x4_t y4 = vget_low_s16(yy);
int16x4_t y8 = vget_high_s16(yy);
int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0);
int32x4_t a1 = vmlal_lane_s16(a0, y4, x4, 0);
int16x4_t y1 = vext_s16(y0, y4, 1);
int16x4_t y5 = vext_s16(y4, y8, 1);
int32x4_t a2 = vmlal_lane_s16(a1, y1, x0, 1);
int32x4_t a3 = vmlal_lane_s16(a2, y5, x4, 1);
int16x4_t y2 = vext_s16(y0, y4, 2);
int16x4_t y6 = vext_s16(y4, y8, 2);
int32x4_t a4 = vmlal_lane_s16(a3, y2, x0, 2);
int32x4_t a5 = vmlal_lane_s16(a4, y6, x4, 2);
int16x4_t y3 = vext_s16(y0, y4, 3);
int16x4_t y7 = vext_s16(y4, y8, 3);
int32x4_t a6 = vmlal_lane_s16(a5, y3, x0, 3);
int32x4_t a7 = vmlal_lane_s16(a6, y7, x4, 3);
y0 = y8;
a = a7;
x += 8;
y += 8;
}
if (j + 4 < len) {
/* Load x[0...3] */
int16x4_t x0 = vld1_s16(x);
/* Load y[4...7] */
int16x4_t y4 = vld1_s16(y);
int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0);
int16x4_t y1 = vext_s16(y0, y4, 1);
int32x4_t a1 = vmlal_lane_s16(a0, y1, x0, 1);
int16x4_t y2 = vext_s16(y0, y4, 2);
int32x4_t a2 = vmlal_lane_s16(a1, y2, x0, 2);
int16x4_t y3 = vext_s16(y0, y4, 3);
int32x4_t a3 = vmlal_lane_s16(a2, y3, x0, 3);
y0 = y4;
a = a3;
x += 4;
y += 4;
j += 4;
}
if (j + 2 < len) {
/* Load x[0...1] */
int16x4x2_t xx = vld2_dup_s16(x);
int16x4_t x0 = xx.val[0];
int16x4_t x1 = xx.val[1];
/* Load y[4...5].
We would like to use vld1_dup_s32(), but casting the pointer would
break strict aliasing rules and potentially have alignment issues.
Fortunately the compiler seems capable of translating this memcpy()
and vdup_n_s32() into the equivalent vld1_dup_s32().*/
int32_t yy;
memcpy(&yy, y, sizeof(yy));
int16x4_t y4 = vreinterpret_s16_s32(vdup_n_s32(yy));
int32x4_t a0 = vmlal_s16(a, y0, x0);
int16x4_t y1 = vext_s16(y0, y4, 1);
/* Replace bottom copy of {y[5], y[4]} in y4 with {y[3], y[2]} from y0,
using VSRI instead of VEXT, since it's a data-processing
instruction. */
y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4),
vreinterpret_s64_s16(y0), 32));
int32x4_t a1 = vmlal_s16(a0, y1, x1);
a = a1;
x += 2;
y += 2;
j += 2;
}
if (j + 1 < len) {
/* Load next x. */
int16x4_t x0 = vld1_dup_s16(x);
int32x4_t a0 = vmlal_s16(a, y0, x0);
/* Load last y. */
int16x4_t y4 = vld1_dup_s16(y);
y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4),
vreinterpret_s64_s16(y0), 16));
a = a0;
x++;
}
/* Load last x. */
int16x4_t x0 = vld1_dup_s16(x);
int32x4_t a0 = vmlal_s16(a, y0, x0);
vst1q_s32(sum, a0);
}
#else
#if defined(__ARM_FEATURE_FMA) && defined(__ARM_ARCH_ISA_A64)
/* If we can, force the compiler to use an FMA instruction rather than break
* vmlaq_f32() into fmul/fadd. */
#ifdef vmlaq_lane_f32
#undef vmlaq_lane_f32
#endif
#define vmlaq_lane_f32(a,b,c,lane) vfmaq_lane_f32(a,b,c,lane)
#endif
/*
* Function: xcorr_kernel_neon_float
* ---------------------------------
* Computes 4 correlation values and stores them in sum[4]
*/
static void xcorr_kernel_neon_float(const float32_t *x, const float32_t *y,
float32_t sum[4], int len) {
float32x4_t YY[3];
float32x4_t YEXT[3];
float32x4_t XX[2];
float32x2_t XX_2;
float32x4_t SUMM;
const float32_t *xi = x;
const float32_t *yi = y;
celt_assert(len>0);
YY[0] = vld1q_f32(yi);
SUMM = vdupq_n_f32(0);
/* Consume 8 elements in x vector and 12 elements in y
* vector. However, the 12'th element never really gets
* touched in this loop. So, if len == 8, then we only
* must access y[0] to y[10]. y[11] must not be accessed
* hence make sure len > 8 and not len >= 8
*/
while (len > 8) {
yi += 4;
YY[1] = vld1q_f32(yi);
yi += 4;
YY[2] = vld1q_f32(yi);
XX[0] = vld1q_f32(xi);
xi += 4;
XX[1] = vld1q_f32(xi);
xi += 4;
SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0);
YEXT[0] = vextq_f32(YY[0], YY[1], 1);
SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1);
YEXT[1] = vextq_f32(YY[0], YY[1], 2);
SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0);
YEXT[2] = vextq_f32(YY[0], YY[1], 3);
SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1);
SUMM = vmlaq_lane_f32(SUMM, YY[1], vget_low_f32(XX[1]), 0);
YEXT[0] = vextq_f32(YY[1], YY[2], 1);
SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[1]), 1);
YEXT[1] = vextq_f32(YY[1], YY[2], 2);
SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[1]), 0);
YEXT[2] = vextq_f32(YY[1], YY[2], 3);
SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[1]), 1);
YY[0] = YY[2];
len -= 8;
}
/* Consume 4 elements in x vector and 8 elements in y
* vector. However, the 8'th element in y never really gets
* touched in this loop. So, if len == 4, then we only
* must access y[0] to y[6]. y[7] must not be accessed
* hence make sure len>4 and not len>=4
*/
if (len > 4) {
yi += 4;
YY[1] = vld1q_f32(yi);
XX[0] = vld1q_f32(xi);
xi += 4;
SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0);
YEXT[0] = vextq_f32(YY[0], YY[1], 1);
SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1);
YEXT[1] = vextq_f32(YY[0], YY[1], 2);
SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0);
YEXT[2] = vextq_f32(YY[0], YY[1], 3);
SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1);
YY[0] = YY[1];
len -= 4;
}
while (--len > 0) {
XX_2 = vld1_dup_f32(xi++);
SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0);
YY[0]= vld1q_f32(++yi);
}
XX_2 = vld1_dup_f32(xi);
SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0);
vst1q_f32(sum, SUMM);
}
void celt_pitch_xcorr_float_neon(const opus_val16 *_x, const opus_val16 *_y,
opus_val32 *xcorr, int len, int max_pitch, int arch) {
int i;
(void)arch;
celt_assert(max_pitch > 0);
celt_sig_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0);
for (i = 0; i < (max_pitch-3); i += 4) {
xcorr_kernel_neon_float((const float32_t *)_x, (const float32_t *)_y+i,
(float32_t *)xcorr+i, len);
}
/* In case max_pitch isn't a multiple of 4, do non-unrolled version. */
for (; i < max_pitch; i++) {
xcorr[i] = celt_inner_prod_neon(_x, _y+i, len);
}
}
#endif

555
local_plugins/opus/android/libs/opus/celt/arm/celt_pitch_xcorr_arm-gnu.S

@ -1,555 +0,0 @@
.syntax unified
@ Copyright (c) 2007-2008 CSIRO
@ Copyright (c) 2007-2009 Xiph.Org Foundation
@ Copyright (c) 2013 Parrot
@ Written by Aurélien Zanelli
@
@ Redistribution and use in source and binary forms, with or without
@ modification, are permitted provided that the following conditions
@ are met:
@
@ - Redistributions of source code must retain the above copyright
@ notice, this list of conditions and the following disclaimer.
@
@ - Redistributions in binary form must reproduce the above copyright
@ notice, this list of conditions and the following disclaimer in the
@ documentation and/or other materials provided with the distribution.
@
@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
@ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
@ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
@ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.text; .p2align 2; .arch armv7-a
.fpu neon
.object_arch armv4t
.include "celt/arm/armopts-gnu.S"
.if OPUS_ARM_MAY_HAVE_EDSP
.global celt_pitch_xcorr_edsp
.endif
.if OPUS_ARM_MAY_HAVE_NEON
.global celt_pitch_xcorr_neon
.endif
.if OPUS_ARM_MAY_HAVE_NEON
@ Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3
.type xcorr_kernel_neon, %function; xcorr_kernel_neon: @ PROC
xcorr_kernel_neon_start:
@ input:
@ r3 = int len
@ r4 = opus_val16 *x
@ r5 = opus_val16 *y
@ q0 = opus_val32 sum[4]
@ output:
@ q0 = opus_val32 sum[4]
@ preserved: r0-r3, r6-r11, d2, q4-q7, q9-q15
@ internal usage:
@ r12 = int j
@ d3 = y_3|y_2|y_1|y_0
@ q2 = y_B|y_A|y_9|y_8|y_7|y_6|y_5|y_4
@ q3 = x_7|x_6|x_5|x_4|x_3|x_2|x_1|x_0
@ q8 = scratch
@
@ Load y[0...3]
@ This requires len>0 to always be valid (which we assert in the C code).
VLD1.16 {d5}, [r5]!
SUBS r12, r3, #8
BLE xcorr_kernel_neon_process4
@ Process 8 samples at a time.
@ This loop loads one y value more than we actually need. Therefore we have to
@ stop as soon as there are 8 or fewer samples left (instead of 7), to avoid
@ reading past the end of the array.
xcorr_kernel_neon_process8:
@ This loop has 19 total instructions (10 cycles to issue, minimum), with
@ - 2 cycles of ARM insrtuctions,
@ - 10 cycles of load/store/byte permute instructions, and
@ - 9 cycles of data processing instructions.
@ On a Cortex A8, we dual-issue the maximum amount (9 cycles) between the
@ latter two categories, meaning the whole loop should run in 10 cycles per
@ iteration, barring cache misses.
@
@ Load x[0...7]
VLD1.16 {d6, d7}, [r4]!
@ Unlike VMOV, VAND is a data processsing instruction (and doesn't get
@ assembled to VMOV, like VORR would), so it dual-issues with the prior VLD1.
VAND d3, d5, d5
SUBS r12, r12, #8
@ Load y[4...11]
VLD1.16 {d4, d5}, [r5]!
VMLAL.S16 q0, d3, d6[0]
VEXT.16 d16, d3, d4, #1
VMLAL.S16 q0, d4, d7[0]
VEXT.16 d17, d4, d5, #1
VMLAL.S16 q0, d16, d6[1]
VEXT.16 d16, d3, d4, #2
VMLAL.S16 q0, d17, d7[1]
VEXT.16 d17, d4, d5, #2
VMLAL.S16 q0, d16, d6[2]
VEXT.16 d16, d3, d4, #3
VMLAL.S16 q0, d17, d7[2]
VEXT.16 d17, d4, d5, #3
VMLAL.S16 q0, d16, d6[3]
VMLAL.S16 q0, d17, d7[3]
BGT xcorr_kernel_neon_process8
@ Process 4 samples here if we have > 4 left (still reading one extra y value).
xcorr_kernel_neon_process4:
ADDS r12, r12, #4
BLE xcorr_kernel_neon_process2
@ Load x[0...3]
VLD1.16 d6, [r4]!
@ Use VAND since it's a data processing instruction again.
VAND d4, d5, d5
SUB r12, r12, #4
@ Load y[4...7]
VLD1.16 d5, [r5]!
VMLAL.S16 q0, d4, d6[0]
VEXT.16 d16, d4, d5, #1
VMLAL.S16 q0, d16, d6[1]
VEXT.16 d16, d4, d5, #2
VMLAL.S16 q0, d16, d6[2]
VEXT.16 d16, d4, d5, #3
VMLAL.S16 q0, d16, d6[3]
@ Process 2 samples here if we have > 2 left (still reading one extra y value).
xcorr_kernel_neon_process2:
ADDS r12, r12, #2
BLE xcorr_kernel_neon_process1
@ Load x[0...1]
VLD2.16 {d6[],d7[]}, [r4]!
@ Use VAND since it's a data processing instruction again.
VAND d4, d5, d5
SUB r12, r12, #2
@ Load y[4...5]
VLD1.32 {d5[]}, [r5]!
VMLAL.S16 q0, d4, d6
VEXT.16 d16, d4, d5, #1
@ Replace bottom copy of {y5,y4} in d5 with {y3,y2} from d4, using VSRI
@ instead of VEXT, since it's a data-processing instruction.
VSRI.64 d5, d4, #32
VMLAL.S16 q0, d16, d7
@ Process 1 sample using the extra y value we loaded above.
xcorr_kernel_neon_process1:
@ Load next *x
VLD1.16 {d6[]}, [r4]!
ADDS r12, r12, #1
@ y[0...3] are left in d5 from prior iteration(s) (if any)
VMLAL.S16 q0, d5, d6
MOVLE pc, lr
@ Now process 1 last sample, not reading ahead.
@ Load last *y
VLD1.16 {d4[]}, [r5]!
VSRI.64 d4, d5, #16
@ Load last *x
VLD1.16 {d6[]}, [r4]!
VMLAL.S16 q0, d4, d6
MOV pc, lr
.size xcorr_kernel_neon, .-xcorr_kernel_neon @ ENDP
@ opus_val32 celt_pitch_xcorr_neon(opus_val16 *_x, opus_val16 *_y,
@ opus_val32 *xcorr, int len, int max_pitch, int arch)
.type celt_pitch_xcorr_neon, %function; celt_pitch_xcorr_neon: @ PROC
@ input:
@ r0 = opus_val16 *_x
@ r1 = opus_val16 *_y
@ r2 = opus_val32 *xcorr
@ r3 = int len
@ output:
@ r0 = int maxcorr
@ internal usage:
@ r4 = opus_val16 *x (for xcorr_kernel_neon())
@ r5 = opus_val16 *y (for xcorr_kernel_neon())
@ r6 = int max_pitch
@ r12 = int j
@ q15 = int maxcorr[4] (q15 is not used by xcorr_kernel_neon())
@ ignored:
@ int arch
STMFD sp!, {r4-r6, lr}
LDR r6, [sp, #16]
VMOV.S32 q15, #1
@ if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done
SUBS r6, r6, #4
BLT celt_pitch_xcorr_neon_process4_done
celt_pitch_xcorr_neon_process4:
@ xcorr_kernel_neon parameters:
@ r3 = len, r4 = _x, r5 = _y, q0 = {0, 0, 0, 0}
MOV r4, r0
MOV r5, r1
VEOR q0, q0, q0
@ xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3.
@ So we don't save/restore any other registers.
BL xcorr_kernel_neon_start
SUBS r6, r6, #4
VST1.32 {q0}, [r2]!
@ _y += 4
ADD r1, r1, #8
VMAX.S32 q15, q15, q0
@ if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done
BGE celt_pitch_xcorr_neon_process4
@ We have less than 4 sums left to compute.
celt_pitch_xcorr_neon_process4_done:
ADDS r6, r6, #4
@ Reduce maxcorr to a single value
VMAX.S32 d30, d30, d31
VPMAX.S32 d30, d30, d30
@ if (max_pitch <= 0) goto celt_pitch_xcorr_neon_done
BLE celt_pitch_xcorr_neon_done
@ Now compute each remaining sum one at a time.
celt_pitch_xcorr_neon_process_remaining:
MOV r4, r0
MOV r5, r1
VMOV.I32 q0, #0
SUBS r12, r3, #8
BLT celt_pitch_xcorr_neon_process_remaining4
@ Sum terms 8 at a time.
celt_pitch_xcorr_neon_process_remaining_loop8:
@ Load x[0...7]
VLD1.16 {q1}, [r4]!
@ Load y[0...7]
VLD1.16 {q2}, [r5]!
SUBS r12, r12, #8
VMLAL.S16 q0, d4, d2
VMLAL.S16 q0, d5, d3
BGE celt_pitch_xcorr_neon_process_remaining_loop8
@ Sum terms 4 at a time.
celt_pitch_xcorr_neon_process_remaining4:
ADDS r12, r12, #4
BLT celt_pitch_xcorr_neon_process_remaining4_done
@ Load x[0...3]
VLD1.16 {d2}, [r4]!
@ Load y[0...3]
VLD1.16 {d3}, [r5]!
SUB r12, r12, #4
VMLAL.S16 q0, d3, d2
celt_pitch_xcorr_neon_process_remaining4_done:
@ Reduce the sum to a single value.
VADD.S32 d0, d0, d1
VPADDL.S32 d0, d0
ADDS r12, r12, #4
BLE celt_pitch_xcorr_neon_process_remaining_loop_done
@ Sum terms 1 at a time.
celt_pitch_xcorr_neon_process_remaining_loop1:
VLD1.16 {d2[]}, [r4]!
VLD1.16 {d3[]}, [r5]!
SUBS r12, r12, #1
VMLAL.S16 q0, d2, d3
BGT celt_pitch_xcorr_neon_process_remaining_loop1
celt_pitch_xcorr_neon_process_remaining_loop_done:
VST1.32 {d0[0]}, [r2]!
VMAX.S32 d30, d30, d0
SUBS r6, r6, #1
@ _y++
ADD r1, r1, #2
@ if (--max_pitch > 0) goto celt_pitch_xcorr_neon_process_remaining
BGT celt_pitch_xcorr_neon_process_remaining
celt_pitch_xcorr_neon_done:
VMOV.32 r0, d30[0]
LDMFD sp!, {r4-r6, pc}
.size celt_pitch_xcorr_neon, .-celt_pitch_xcorr_neon @ ENDP
.endif
.if OPUS_ARM_MAY_HAVE_EDSP
@ This will get used on ARMv7 devices without NEON, so it has been optimized
@ to take advantage of dual-issuing where possible.
.type xcorr_kernel_edsp, %function; xcorr_kernel_edsp: @ PROC
xcorr_kernel_edsp_start:
@ input:
@ r3 = int len
@ r4 = opus_val16 *_x (must be 32-bit aligned)
@ r5 = opus_val16 *_y (must be 32-bit aligned)
@ r6...r9 = opus_val32 sum[4]
@ output:
@ r6...r9 = opus_val32 sum[4]
@ preserved: r0-r5
@ internal usage
@ r2 = int j
@ r12,r14 = opus_val16 x[4]
@ r10,r11 = opus_val16 y[4]
STMFD sp!, {r2,r4,r5,lr}
LDR r10, [r5], #4 @ Load y[0...1]
SUBS r2, r3, #4 @ j = len-4
LDR r11, [r5], #4 @ Load y[2...3]
BLE xcorr_kernel_edsp_process4_done
LDR r12, [r4], #4 @ Load x[0...1]
@ Stall
xcorr_kernel_edsp_process4:
@ The multiplies must issue from pipeline 0, and can't dual-issue with each
@ other. Every other instruction here dual-issues with a multiply, and is
@ thus "free". There should be no stalls in the body of the loop.
SMLABB r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x_0,y_0)
LDR r14, [r4], #4 @ Load x[2...3]
SMLABT r7, r12, r10, r7 @ sum[1] = MAC16_16(sum[1],x_0,y_1)
SUBS r2, r2, #4 @ j-=4
SMLABB r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x_0,y_2)
SMLABT r9, r12, r11, r9 @ sum[3] = MAC16_16(sum[3],x_0,y_3)
SMLATT r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x_1,y_1)
LDR r10, [r5], #4 @ Load y[4...5]
SMLATB r7, r12, r11, r7 @ sum[1] = MAC16_16(sum[1],x_1,y_2)
SMLATT r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x_1,y_3)
SMLATB r9, r12, r10, r9 @ sum[3] = MAC16_16(sum[3],x_1,y_4)
LDRGT r12, [r4], #4 @ Load x[0...1]
SMLABB r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],x_2,y_2)
SMLABT r7, r14, r11, r7 @ sum[1] = MAC16_16(sum[1],x_2,y_3)
SMLABB r8, r14, r10, r8 @ sum[2] = MAC16_16(sum[2],x_2,y_4)
SMLABT r9, r14, r10, r9 @ sum[3] = MAC16_16(sum[3],x_2,y_5)
SMLATT r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],x_3,y_3)
LDR r11, [r5], #4 @ Load y[6...7]
SMLATB r7, r14, r10, r7 @ sum[1] = MAC16_16(sum[1],x_3,y_4)
SMLATT r8, r14, r10, r8 @ sum[2] = MAC16_16(sum[2],x_3,y_5)
SMLATB r9, r14, r11, r9 @ sum[3] = MAC16_16(sum[3],x_3,y_6)
BGT xcorr_kernel_edsp_process4
xcorr_kernel_edsp_process4_done:
ADDS r2, r2, #4
BLE xcorr_kernel_edsp_done
LDRH r12, [r4], #2 @ r12 = *x++
SUBS r2, r2, #1 @ j--
@ Stall
SMLABB r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x,y_0)
LDRHGT r14, [r4], #2 @ r14 = *x++
SMLABT r7, r12, r10, r7 @ sum[1] = MAC16_16(sum[1],x,y_1)
SMLABB r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x,y_2)
SMLABT r9, r12, r11, r9 @ sum[3] = MAC16_16(sum[3],x,y_3)
BLE xcorr_kernel_edsp_done
SMLABT r6, r14, r10, r6 @ sum[0] = MAC16_16(sum[0],x,y_1)
SUBS r2, r2, #1 @ j--
SMLABB r7, r14, r11, r7 @ sum[1] = MAC16_16(sum[1],x,y_2)
LDRH r10, [r5], #2 @ r10 = y_4 = *y++
SMLABT r8, r14, r11, r8 @ sum[2] = MAC16_16(sum[2],x,y_3)
LDRHGT r12, [r4], #2 @ r12 = *x++
SMLABB r9, r14, r10, r9 @ sum[3] = MAC16_16(sum[3],x,y_4)
BLE xcorr_kernel_edsp_done
SMLABB r6, r12, r11, r6 @ sum[0] = MAC16_16(sum[0],tmp,y_2)
CMP r2, #1 @ j--
SMLABT r7, r12, r11, r7 @ sum[1] = MAC16_16(sum[1],tmp,y_3)
LDRH r2, [r5], #2 @ r2 = y_5 = *y++
SMLABB r8, r12, r10, r8 @ sum[2] = MAC16_16(sum[2],tmp,y_4)
LDRHGT r14, [r4] @ r14 = *x
SMLABB r9, r12, r2, r9 @ sum[3] = MAC16_16(sum[3],tmp,y_5)
BLE xcorr_kernel_edsp_done
SMLABT r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],tmp,y_3)
LDRH r11, [r5] @ r11 = y_6 = *y
SMLABB r7, r14, r10, r7 @ sum[1] = MAC16_16(sum[1],tmp,y_4)
SMLABB r8, r14, r2, r8 @ sum[2] = MAC16_16(sum[2],tmp,y_5)
SMLABB r9, r14, r11, r9 @ sum[3] = MAC16_16(sum[3],tmp,y_6)
xcorr_kernel_edsp_done:
LDMFD sp!, {r2,r4,r5,pc}
.size xcorr_kernel_edsp, .-xcorr_kernel_edsp @ ENDP
.type celt_pitch_xcorr_edsp, %function; celt_pitch_xcorr_edsp: @ PROC
@ input:
@ r0 = opus_val16 *_x (must be 32-bit aligned)
@ r1 = opus_val16 *_y (only needs to be 16-bit aligned)
@ r2 = opus_val32 *xcorr
@ r3 = int len
@ output:
@ r0 = maxcorr
@ internal usage
@ r4 = opus_val16 *x
@ r5 = opus_val16 *y
@ r6 = opus_val32 sum0
@ r7 = opus_val32 sum1
@ r8 = opus_val32 sum2
@ r9 = opus_val32 sum3
@ r1 = int max_pitch
@ r12 = int j
@ ignored:
@ int arch
STMFD sp!, {r4-r11, lr}
MOV r5, r1
LDR r1, [sp, #36]
MOV r4, r0
TST r5, #3
@ maxcorr = 1
MOV r0, #1
BEQ celt_pitch_xcorr_edsp_process1u_done
@ Compute one sum at the start to make y 32-bit aligned.
SUBS r12, r3, #4
@ r14 = sum = 0
MOV r14, #0
LDRH r8, [r5], #2
BLE celt_pitch_xcorr_edsp_process1u_loop4_done
LDR r6, [r4], #4
MOV r8, r8, LSL #16
celt_pitch_xcorr_edsp_process1u_loop4:
LDR r9, [r5], #4
SMLABT r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0)
LDR r7, [r4], #4
SMLATB r14, r6, r9, r14 @ sum = MAC16_16(sum, x_1, y_1)
LDR r8, [r5], #4
SMLABT r14, r7, r9, r14 @ sum = MAC16_16(sum, x_2, y_2)
SUBS r12, r12, #4 @ j-=4
SMLATB r14, r7, r8, r14 @ sum = MAC16_16(sum, x_3, y_3)
LDRGT r6, [r4], #4
BGT celt_pitch_xcorr_edsp_process1u_loop4
MOV r8, r8, LSR #16
celt_pitch_xcorr_edsp_process1u_loop4_done:
ADDS r12, r12, #4
celt_pitch_xcorr_edsp_process1u_loop1:
LDRHGE r6, [r4], #2
@ Stall
SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, *x, *y)
SUBSGE r12, r12, #1
LDRHGT r8, [r5], #2
BGT celt_pitch_xcorr_edsp_process1u_loop1
@ Restore _x
SUB r4, r4, r3, LSL #1
@ Restore and advance _y
SUB r5, r5, r3, LSL #1
@ maxcorr = max(maxcorr, sum)
CMP r0, r14
ADD r5, r5, #2
MOVLT r0, r14
SUBS r1, r1, #1
@ xcorr[i] = sum
STR r14, [r2], #4
BLE celt_pitch_xcorr_edsp_done
celt_pitch_xcorr_edsp_process1u_done:
@ if (max_pitch < 4) goto celt_pitch_xcorr_edsp_process2
SUBS r1, r1, #4
BLT celt_pitch_xcorr_edsp_process2
celt_pitch_xcorr_edsp_process4:
@ xcorr_kernel_edsp parameters:
@ r3 = len, r4 = _x, r5 = _y, r6...r9 = sum[4] = {0, 0, 0, 0}
MOV r6, #0
MOV r7, #0
MOV r8, #0
MOV r9, #0
BL xcorr_kernel_edsp_start @ xcorr_kernel_edsp(_x, _y+i, xcorr+i, len)
@ maxcorr = max(maxcorr, sum0, sum1, sum2, sum3)
CMP r0, r6
@ _y+=4
ADD r5, r5, #8
MOVLT r0, r6
CMP r0, r7
MOVLT r0, r7
CMP r0, r8
MOVLT r0, r8
CMP r0, r9
MOVLT r0, r9
STMIA r2!, {r6-r9}
SUBS r1, r1, #4
BGE celt_pitch_xcorr_edsp_process4
celt_pitch_xcorr_edsp_process2:
ADDS r1, r1, #2
BLT celt_pitch_xcorr_edsp_process1a
SUBS r12, r3, #4
@ {r10, r11} = {sum0, sum1} = {0, 0}
MOV r10, #0
MOV r11, #0
LDR r8, [r5], #4
BLE celt_pitch_xcorr_edsp_process2_loop_done
LDR r6, [r4], #4
LDR r9, [r5], #4
celt_pitch_xcorr_edsp_process2_loop4:
SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0)
LDR r7, [r4], #4
SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1)
SUBS r12, r12, #4 @ j-=4
SMLATT r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_1, y_1)
LDR r8, [r5], #4
SMLATB r11, r6, r9, r11 @ sum1 = MAC16_16(sum1, x_1, y_2)
LDRGT r6, [r4], #4
SMLABB r10, r7, r9, r10 @ sum0 = MAC16_16(sum0, x_2, y_2)
SMLABT r11, r7, r9, r11 @ sum1 = MAC16_16(sum1, x_2, y_3)
SMLATT r10, r7, r9, r10 @ sum0 = MAC16_16(sum0, x_3, y_3)
LDRGT r9, [r5], #4
SMLATB r11, r7, r8, r11 @ sum1 = MAC16_16(sum1, x_3, y_4)
BGT celt_pitch_xcorr_edsp_process2_loop4
celt_pitch_xcorr_edsp_process2_loop_done:
ADDS r12, r12, #2
BLE celt_pitch_xcorr_edsp_process2_1
LDR r6, [r4], #4
@ Stall
SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0)
LDR r9, [r5], #4
SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1)
SUB r12, r12, #2
SMLATT r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_1, y_1)
MOV r8, r9
SMLATB r11, r6, r9, r11 @ sum1 = MAC16_16(sum1, x_1, y_2)
celt_pitch_xcorr_edsp_process2_1:
LDRH r6, [r4], #2
ADDS r12, r12, #1
@ Stall
SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0)
LDRHGT r7, [r4], #2
SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1)
BLE celt_pitch_xcorr_edsp_process2_done
LDRH r9, [r5], #2
SMLABT r10, r7, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_1)
SMLABB r11, r7, r9, r11 @ sum1 = MAC16_16(sum1, x_0, y_2)
celt_pitch_xcorr_edsp_process2_done:
@ Restore _x
SUB r4, r4, r3, LSL #1
@ Restore and advance _y
SUB r5, r5, r3, LSL #1
@ maxcorr = max(maxcorr, sum0)
CMP r0, r10
ADD r5, r5, #2
MOVLT r0, r10
SUB r1, r1, #2
@ maxcorr = max(maxcorr, sum1)
CMP r0, r11
@ xcorr[i] = sum
STR r10, [r2], #4
MOVLT r0, r11
STR r11, [r2], #4
celt_pitch_xcorr_edsp_process1a:
ADDS r1, r1, #1
BLT celt_pitch_xcorr_edsp_done
SUBS r12, r3, #4
@ r14 = sum = 0
MOV r14, #0
BLT celt_pitch_xcorr_edsp_process1a_loop_done
LDR r6, [r4], #4
LDR r8, [r5], #4
LDR r7, [r4], #4
LDR r9, [r5], #4
celt_pitch_xcorr_edsp_process1a_loop4:
SMLABB r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0)
SUBS r12, r12, #4 @ j-=4
SMLATT r14, r6, r8, r14 @ sum = MAC16_16(sum, x_1, y_1)
LDRGE r6, [r4], #4
SMLABB r14, r7, r9, r14 @ sum = MAC16_16(sum, x_2, y_2)
LDRGE r8, [r5], #4
SMLATT r14, r7, r9, r14 @ sum = MAC16_16(sum, x_3, y_3)
LDRGE r7, [r4], #4
LDRGE r9, [r5], #4
BGE celt_pitch_xcorr_edsp_process1a_loop4
celt_pitch_xcorr_edsp_process1a_loop_done:
ADDS r12, r12, #2
LDRGE r6, [r4], #4
LDRGE r8, [r5], #4
@ Stall
SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0)
SUBGE r12, r12, #2
SMLATTGE r14, r6, r8, r14 @ sum = MAC16_16(sum, x_1, y_1)
ADDS r12, r12, #1
LDRHGE r6, [r4], #2
LDRHGE r8, [r5], #2
@ Stall
SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, *x, *y)
@ maxcorr = max(maxcorr, sum)
CMP r0, r14
@ xcorr[i] = sum
STR r14, [r2], #4
MOVLT r0, r14
celt_pitch_xcorr_edsp_done:
LDMFD sp!, {r4-r11, pc}
.size celt_pitch_xcorr_edsp, .-celt_pitch_xcorr_edsp @ ENDP
.endif
@ END:
.section .note.GNU-stack,"",%progbits

551
local_plugins/opus/android/libs/opus/celt/arm/celt_pitch_xcorr_arm.s

@ -1,551 +0,0 @@
; Copyright (c) 2007-2008 CSIRO
; Copyright (c) 2007-2009 Xiph.Org Foundation
; Copyright (c) 2013 Parrot
; Written by Aurélien Zanelli
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions
; are met:
;
; - Redistributions of source code must retain the above copyright
; notice, this list of conditions and the following disclaimer.
;
; - Redistributions in binary form must reproduce the above copyright
; notice, this list of conditions and the following disclaimer in the
; documentation and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
; ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
; OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
AREA |.text|, CODE, READONLY
GET celt/arm/armopts.s
IF OPUS_ARM_MAY_HAVE_EDSP
EXPORT celt_pitch_xcorr_edsp
ENDIF
IF OPUS_ARM_MAY_HAVE_NEON
EXPORT celt_pitch_xcorr_neon
ENDIF
IF OPUS_ARM_MAY_HAVE_NEON
; Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3
xcorr_kernel_neon PROC
xcorr_kernel_neon_start
; input:
; r3 = int len
; r4 = opus_val16 *x
; r5 = opus_val16 *y
; q0 = opus_val32 sum[4]
; output:
; q0 = opus_val32 sum[4]
; preserved: r0-r3, r6-r11, d2, q4-q7, q9-q15
; internal usage:
; r12 = int j
; d3 = y_3|y_2|y_1|y_0
; q2 = y_B|y_A|y_9|y_8|y_7|y_6|y_5|y_4
; q3 = x_7|x_6|x_5|x_4|x_3|x_2|x_1|x_0
; q8 = scratch
;
; Load y[0...3]
; This requires len>0 to always be valid (which we assert in the C code).
VLD1.16 {d5}, [r5]!
SUBS r12, r3, #8
BLE xcorr_kernel_neon_process4
; Process 8 samples at a time.
; This loop loads one y value more than we actually need. Therefore we have to
; stop as soon as there are 8 or fewer samples left (instead of 7), to avoid
; reading past the end of the array.
xcorr_kernel_neon_process8
; This loop has 19 total instructions (10 cycles to issue, minimum), with
; - 2 cycles of ARM insrtuctions,
; - 10 cycles of load/store/byte permute instructions, and
; - 9 cycles of data processing instructions.
; On a Cortex A8, we dual-issue the maximum amount (9 cycles) between the
; latter two categories, meaning the whole loop should run in 10 cycles per
; iteration, barring cache misses.
;
; Load x[0...7]
VLD1.16 {d6, d7}, [r4]!
; Unlike VMOV, VAND is a data processsing instruction (and doesn't get
; assembled to VMOV, like VORR would), so it dual-issues with the prior VLD1.
VAND d3, d5, d5
SUBS r12, r12, #8
; Load y[4...11]
VLD1.16 {d4, d5}, [r5]!
VMLAL.S16 q0, d3, d6[0]
VEXT.16 d16, d3, d4, #1
VMLAL.S16 q0, d4, d7[0]
VEXT.16 d17, d4, d5, #1
VMLAL.S16 q0, d16, d6[1]
VEXT.16 d16, d3, d4, #2
VMLAL.S16 q0, d17, d7[1]
VEXT.16 d17, d4, d5, #2
VMLAL.S16 q0, d16, d6[2]
VEXT.16 d16, d3, d4, #3
VMLAL.S16 q0, d17, d7[2]
VEXT.16 d17, d4, d5, #3
VMLAL.S16 q0, d16, d6[3]
VMLAL.S16 q0, d17, d7[3]
BGT xcorr_kernel_neon_process8
; Process 4 samples here if we have > 4 left (still reading one extra y value).
xcorr_kernel_neon_process4
ADDS r12, r12, #4
BLE xcorr_kernel_neon_process2
; Load x[0...3]
VLD1.16 d6, [r4]!
; Use VAND since it's a data processing instruction again.
VAND d4, d5, d5
SUB r12, r12, #4
; Load y[4...7]
VLD1.16 d5, [r5]!
VMLAL.S16 q0, d4, d6[0]
VEXT.16 d16, d4, d5, #1
VMLAL.S16 q0, d16, d6[1]
VEXT.16 d16, d4, d5, #2
VMLAL.S16 q0, d16, d6[2]
VEXT.16 d16, d4, d5, #3
VMLAL.S16 q0, d16, d6[3]
; Process 2 samples here if we have > 2 left (still reading one extra y value).
xcorr_kernel_neon_process2
ADDS r12, r12, #2
BLE xcorr_kernel_neon_process1
; Load x[0...1]
VLD2.16 {d6[],d7[]}, [r4]!
; Use VAND since it's a data processing instruction again.
VAND d4, d5, d5
SUB r12, r12, #2
; Load y[4...5]
VLD1.32 {d5[]}, [r5]!
VMLAL.S16 q0, d4, d6
VEXT.16 d16, d4, d5, #1
; Replace bottom copy of {y5,y4} in d5 with {y3,y2} from d4, using VSRI
; instead of VEXT, since it's a data-processing instruction.
VSRI.64 d5, d4, #32
VMLAL.S16 q0, d16, d7
; Process 1 sample using the extra y value we loaded above.
xcorr_kernel_neon_process1
; Load next *x
VLD1.16 {d6[]}, [r4]!
ADDS r12, r12, #1
; y[0...3] are left in d5 from prior iteration(s) (if any)
VMLAL.S16 q0, d5, d6
MOVLE pc, lr
; Now process 1 last sample, not reading ahead.
; Load last *y
VLD1.16 {d4[]}, [r5]!
VSRI.64 d4, d5, #16
; Load last *x
VLD1.16 {d6[]}, [r4]!
VMLAL.S16 q0, d4, d6
MOV pc, lr
ENDP
; opus_val32 celt_pitch_xcorr_neon(opus_val16 *_x, opus_val16 *_y,
; opus_val32 *xcorr, int len, int max_pitch, int arch)
celt_pitch_xcorr_neon PROC
; input:
; r0 = opus_val16 *_x
; r1 = opus_val16 *_y
; r2 = opus_val32 *xcorr
; r3 = int len
; output:
; r0 = int maxcorr
; internal usage:
; r4 = opus_val16 *x (for xcorr_kernel_neon())
; r5 = opus_val16 *y (for xcorr_kernel_neon())
; r6 = int max_pitch
; r12 = int j
; q15 = int maxcorr[4] (q15 is not used by xcorr_kernel_neon())
; ignored:
; int arch
STMFD sp!, {r4-r6, lr}
LDR r6, [sp, #16]
VMOV.S32 q15, #1
; if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done
SUBS r6, r6, #4
BLT celt_pitch_xcorr_neon_process4_done
celt_pitch_xcorr_neon_process4
; xcorr_kernel_neon parameters:
; r3 = len, r4 = _x, r5 = _y, q0 = {0, 0, 0, 0}
MOV r4, r0
MOV r5, r1
VEOR q0, q0, q0
; xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3.
; So we don't save/restore any other registers.
BL xcorr_kernel_neon_start
SUBS r6, r6, #4
VST1.32 {q0}, [r2]!
; _y += 4
ADD r1, r1, #8
VMAX.S32 q15, q15, q0
; if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done
BGE celt_pitch_xcorr_neon_process4
; We have less than 4 sums left to compute.
celt_pitch_xcorr_neon_process4_done
ADDS r6, r6, #4
; Reduce maxcorr to a single value
VMAX.S32 d30, d30, d31
VPMAX.S32 d30, d30, d30
; if (max_pitch <= 0) goto celt_pitch_xcorr_neon_done
BLE celt_pitch_xcorr_neon_done
; Now compute each remaining sum one at a time.
celt_pitch_xcorr_neon_process_remaining
MOV r4, r0
MOV r5, r1
VMOV.I32 q0, #0
SUBS r12, r3, #8
BLT celt_pitch_xcorr_neon_process_remaining4
; Sum terms 8 at a time.
celt_pitch_xcorr_neon_process_remaining_loop8
; Load x[0...7]
VLD1.16 {q1}, [r4]!
; Load y[0...7]
VLD1.16 {q2}, [r5]!
SUBS r12, r12, #8
VMLAL.S16 q0, d4, d2
VMLAL.S16 q0, d5, d3
BGE celt_pitch_xcorr_neon_process_remaining_loop8
; Sum terms 4 at a time.
celt_pitch_xcorr_neon_process_remaining4
ADDS r12, r12, #4
BLT celt_pitch_xcorr_neon_process_remaining4_done
; Load x[0...3]
VLD1.16 {d2}, [r4]!
; Load y[0...3]
VLD1.16 {d3}, [r5]!
SUB r12, r12, #4
VMLAL.S16 q0, d3, d2
celt_pitch_xcorr_neon_process_remaining4_done
; Reduce the sum to a single value.
VADD.S32 d0, d0, d1
VPADDL.S32 d0, d0
ADDS r12, r12, #4
BLE celt_pitch_xcorr_neon_process_remaining_loop_done
; Sum terms 1 at a time.
celt_pitch_xcorr_neon_process_remaining_loop1
VLD1.16 {d2[]}, [r4]!
VLD1.16 {d3[]}, [r5]!
SUBS r12, r12, #1
VMLAL.S16 q0, d2, d3
BGT celt_pitch_xcorr_neon_process_remaining_loop1
celt_pitch_xcorr_neon_process_remaining_loop_done
VST1.32 {d0[0]}, [r2]!
VMAX.S32 d30, d30, d0
SUBS r6, r6, #1
; _y++
ADD r1, r1, #2
; if (--max_pitch > 0) goto celt_pitch_xcorr_neon_process_remaining
BGT celt_pitch_xcorr_neon_process_remaining
celt_pitch_xcorr_neon_done
VMOV.32 r0, d30[0]
LDMFD sp!, {r4-r6, pc}
ENDP
ENDIF
IF OPUS_ARM_MAY_HAVE_EDSP
; This will get used on ARMv7 devices without NEON, so it has been optimized
; to take advantage of dual-issuing where possible.
xcorr_kernel_edsp PROC
xcorr_kernel_edsp_start
; input:
; r3 = int len
; r4 = opus_val16 *_x (must be 32-bit aligned)
; r5 = opus_val16 *_y (must be 32-bit aligned)
; r6...r9 = opus_val32 sum[4]
; output:
; r6...r9 = opus_val32 sum[4]
; preserved: r0-r5
; internal usage
; r2 = int j
; r12,r14 = opus_val16 x[4]
; r10,r11 = opus_val16 y[4]
STMFD sp!, {r2,r4,r5,lr}
LDR r10, [r5], #4 ; Load y[0...1]
SUBS r2, r3, #4 ; j = len-4
LDR r11, [r5], #4 ; Load y[2...3]
BLE xcorr_kernel_edsp_process4_done
LDR r12, [r4], #4 ; Load x[0...1]
; Stall
xcorr_kernel_edsp_process4
; The multiplies must issue from pipeline 0, and can't dual-issue with each
; other. Every other instruction here dual-issues with a multiply, and is
; thus "free". There should be no stalls in the body of the loop.
SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x_0,y_0)
LDR r14, [r4], #4 ; Load x[2...3]
SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x_0,y_1)
SUBS r2, r2, #4 ; j-=4
SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x_0,y_2)
SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x_0,y_3)
SMLATT r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x_1,y_1)
LDR r10, [r5], #4 ; Load y[4...5]
SMLATB r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],x_1,y_2)
SMLATT r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x_1,y_3)
SMLATB r9, r12, r10, r9 ; sum[3] = MAC16_16(sum[3],x_1,y_4)
LDRGT r12, [r4], #4 ; Load x[0...1]
SMLABB r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],x_2,y_2)
SMLABT r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x_2,y_3)
SMLABB r8, r14, r10, r8 ; sum[2] = MAC16_16(sum[2],x_2,y_4)
SMLABT r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x_2,y_5)
SMLATT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],x_3,y_3)
LDR r11, [r5], #4 ; Load y[6...7]
SMLATB r7, r14, r10, r7 ; sum[1] = MAC16_16(sum[1],x_3,y_4)
SMLATT r8, r14, r10, r8 ; sum[2] = MAC16_16(sum[2],x_3,y_5)
SMLATB r9, r14, r11, r9 ; sum[3] = MAC16_16(sum[3],x_3,y_6)
BGT xcorr_kernel_edsp_process4
xcorr_kernel_edsp_process4_done
ADDS r2, r2, #4
BLE xcorr_kernel_edsp_done
LDRH r12, [r4], #2 ; r12 = *x++
SUBS r2, r2, #1 ; j--
; Stall
SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_0)
LDRHGT r14, [r4], #2 ; r14 = *x++
SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x,y_1)
SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_2)
SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x,y_3)
BLE xcorr_kernel_edsp_done
SMLABT r6, r14, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_1)
SUBS r2, r2, #1 ; j--
SMLABB r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x,y_2)
LDRH r10, [r5], #2 ; r10 = y_4 = *y++
SMLABT r8, r14, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_3)
LDRHGT r12, [r4], #2 ; r12 = *x++
SMLABB r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x,y_4)
BLE xcorr_kernel_edsp_done
SMLABB r6, r12, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_2)
CMP r2, #1 ; j--
SMLABT r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_3)
LDRH r2, [r5], #2 ; r2 = y_5 = *y++
SMLABB r8, r12, r10, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_4)
LDRHGT r14, [r4] ; r14 = *x
SMLABB r9, r12, r2, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_5)
BLE xcorr_kernel_edsp_done
SMLABT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_3)
LDRH r11, [r5] ; r11 = y_6 = *y
SMLABB r7, r14, r10, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_4)
SMLABB r8, r14, r2, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_5)
SMLABB r9, r14, r11, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_6)
xcorr_kernel_edsp_done
LDMFD sp!, {r2,r4,r5,pc}
ENDP
celt_pitch_xcorr_edsp PROC
; input:
; r0 = opus_val16 *_x (must be 32-bit aligned)
; r1 = opus_val16 *_y (only needs to be 16-bit aligned)
; r2 = opus_val32 *xcorr
; r3 = int len
; output:
; r0 = maxcorr
; internal usage
; r4 = opus_val16 *x
; r5 = opus_val16 *y
; r6 = opus_val32 sum0
; r7 = opus_val32 sum1
; r8 = opus_val32 sum2
; r9 = opus_val32 sum3
; r1 = int max_pitch
; r12 = int j
; ignored:
; int arch
STMFD sp!, {r4-r11, lr}
MOV r5, r1
LDR r1, [sp, #36]
MOV r4, r0
TST r5, #3
; maxcorr = 1
MOV r0, #1
BEQ celt_pitch_xcorr_edsp_process1u_done
; Compute one sum at the start to make y 32-bit aligned.
SUBS r12, r3, #4
; r14 = sum = 0
MOV r14, #0
LDRH r8, [r5], #2
BLE celt_pitch_xcorr_edsp_process1u_loop4_done
LDR r6, [r4], #4
MOV r8, r8, LSL #16
celt_pitch_xcorr_edsp_process1u_loop4
LDR r9, [r5], #4
SMLABT r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0)
LDR r7, [r4], #4
SMLATB r14, r6, r9, r14 ; sum = MAC16_16(sum, x_1, y_1)
LDR r8, [r5], #4
SMLABT r14, r7, r9, r14 ; sum = MAC16_16(sum, x_2, y_2)
SUBS r12, r12, #4 ; j-=4
SMLATB r14, r7, r8, r14 ; sum = MAC16_16(sum, x_3, y_3)
LDRGT r6, [r4], #4
BGT celt_pitch_xcorr_edsp_process1u_loop4
MOV r8, r8, LSR #16
celt_pitch_xcorr_edsp_process1u_loop4_done
ADDS r12, r12, #4
celt_pitch_xcorr_edsp_process1u_loop1
LDRHGE r6, [r4], #2
; Stall
SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y)
SUBSGE r12, r12, #1
LDRHGT r8, [r5], #2
BGT celt_pitch_xcorr_edsp_process1u_loop1
; Restore _x
SUB r4, r4, r3, LSL #1
; Restore and advance _y
SUB r5, r5, r3, LSL #1
; maxcorr = max(maxcorr, sum)
CMP r0, r14
ADD r5, r5, #2
MOVLT r0, r14
SUBS r1, r1, #1
; xcorr[i] = sum
STR r14, [r2], #4
BLE celt_pitch_xcorr_edsp_done
celt_pitch_xcorr_edsp_process1u_done
; if (max_pitch < 4) goto celt_pitch_xcorr_edsp_process2
SUBS r1, r1, #4
BLT celt_pitch_xcorr_edsp_process2
celt_pitch_xcorr_edsp_process4
; xcorr_kernel_edsp parameters:
; r3 = len, r4 = _x, r5 = _y, r6...r9 = sum[4] = {0, 0, 0, 0}
MOV r6, #0
MOV r7, #0
MOV r8, #0
MOV r9, #0
BL xcorr_kernel_edsp_start ; xcorr_kernel_edsp(_x, _y+i, xcorr+i, len)
; maxcorr = max(maxcorr, sum0, sum1, sum2, sum3)
CMP r0, r6
; _y+=4
ADD r5, r5, #8
MOVLT r0, r6
CMP r0, r7
MOVLT r0, r7
CMP r0, r8
MOVLT r0, r8
CMP r0, r9
MOVLT r0, r9
STMIA r2!, {r6-r9}
SUBS r1, r1, #4
BGE celt_pitch_xcorr_edsp_process4
celt_pitch_xcorr_edsp_process2
ADDS r1, r1, #2
BLT celt_pitch_xcorr_edsp_process1a
SUBS r12, r3, #4
; {r10, r11} = {sum0, sum1} = {0, 0}
MOV r10, #0
MOV r11, #0
LDR r8, [r5], #4
BLE celt_pitch_xcorr_edsp_process2_loop_done
LDR r6, [r4], #4
LDR r9, [r5], #4
celt_pitch_xcorr_edsp_process2_loop4
SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0)
LDR r7, [r4], #4
SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1)
SUBS r12, r12, #4 ; j-=4
SMLATT r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_1, y_1)
LDR r8, [r5], #4
SMLATB r11, r6, r9, r11 ; sum1 = MAC16_16(sum1, x_1, y_2)
LDRGT r6, [r4], #4
SMLABB r10, r7, r9, r10 ; sum0 = MAC16_16(sum0, x_2, y_2)
SMLABT r11, r7, r9, r11 ; sum1 = MAC16_16(sum1, x_2, y_3)
SMLATT r10, r7, r9, r10 ; sum0 = MAC16_16(sum0, x_3, y_3)
LDRGT r9, [r5], #4
SMLATB r11, r7, r8, r11 ; sum1 = MAC16_16(sum1, x_3, y_4)
BGT celt_pitch_xcorr_edsp_process2_loop4
celt_pitch_xcorr_edsp_process2_loop_done
ADDS r12, r12, #2
BLE celt_pitch_xcorr_edsp_process2_1
LDR r6, [r4], #4
; Stall
SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0)
LDR r9, [r5], #4
SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1)
SUB r12, r12, #2
SMLATT r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_1, y_1)
MOV r8, r9
SMLATB r11, r6, r9, r11 ; sum1 = MAC16_16(sum1, x_1, y_2)
celt_pitch_xcorr_edsp_process2_1
LDRH r6, [r4], #2
ADDS r12, r12, #1
; Stall
SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0)
LDRHGT r7, [r4], #2
SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1)
BLE celt_pitch_xcorr_edsp_process2_done
LDRH r9, [r5], #2
SMLABT r10, r7, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_1)
SMLABB r11, r7, r9, r11 ; sum1 = MAC16_16(sum1, x_0, y_2)
celt_pitch_xcorr_edsp_process2_done
; Restore _x
SUB r4, r4, r3, LSL #1
; Restore and advance _y
SUB r5, r5, r3, LSL #1
; maxcorr = max(maxcorr, sum0)
CMP r0, r10
ADD r5, r5, #2
MOVLT r0, r10
SUB r1, r1, #2
; maxcorr = max(maxcorr, sum1)
CMP r0, r11
; xcorr[i] = sum
STR r10, [r2], #4
MOVLT r0, r11
STR r11, [r2], #4
celt_pitch_xcorr_edsp_process1a
ADDS r1, r1, #1
BLT celt_pitch_xcorr_edsp_done
SUBS r12, r3, #4
; r14 = sum = 0
MOV r14, #0
BLT celt_pitch_xcorr_edsp_process1a_loop_done
LDR r6, [r4], #4
LDR r8, [r5], #4
LDR r7, [r4], #4
LDR r9, [r5], #4
celt_pitch_xcorr_edsp_process1a_loop4
SMLABB r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0)
SUBS r12, r12, #4 ; j-=4
SMLATT r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1)
LDRGE r6, [r4], #4
SMLABB r14, r7, r9, r14 ; sum = MAC16_16(sum, x_2, y_2)
LDRGE r8, [r5], #4
SMLATT r14, r7, r9, r14 ; sum = MAC16_16(sum, x_3, y_3)
LDRGE r7, [r4], #4
LDRGE r9, [r5], #4
BGE celt_pitch_xcorr_edsp_process1a_loop4
celt_pitch_xcorr_edsp_process1a_loop_done
ADDS r12, r12, #2
LDRGE r6, [r4], #4
LDRGE r8, [r5], #4
; Stall
SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0)
SUBGE r12, r12, #2
SMLATTGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1)
ADDS r12, r12, #1
LDRHGE r6, [r4], #2
LDRHGE r8, [r5], #2
; Stall
SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y)
; maxcorr = max(maxcorr, sum)
CMP r0, r14
; xcorr[i] = sum
STR r14, [r2], #4
MOVLT r0, r14
celt_pitch_xcorr_edsp_done
LDMFD sp!, {r4-r11, pc}
ENDP
ENDIF
END

71
local_plugins/opus/android/libs/opus/celt/arm/fft_arm.h

@ -1,71 +0,0 @@
/* Copyright (c) 2015 Xiph.Org Foundation
Written by Viswanath Puttagunta */
/**
@file fft_arm.h
@brief ARM Neon Intrinsic optimizations for fft using NE10 library
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !defined(FFT_ARM_H)
#define FFT_ARM_H
#include "kiss_fft.h"
#if defined(HAVE_ARM_NE10)
int opus_fft_alloc_arm_neon(kiss_fft_state *st);
void opus_fft_free_arm_neon(kiss_fft_state *st);
void opus_fft_neon(const kiss_fft_state *st,
const kiss_fft_cpx *fin,
kiss_fft_cpx *fout);
void opus_ifft_neon(const kiss_fft_state *st,
const kiss_fft_cpx *fin,
kiss_fft_cpx *fout);
#if !defined(OPUS_HAVE_RTCD)
#define OVERRIDE_OPUS_FFT (1)
#define opus_fft_alloc_arch(_st, arch) \
((void)(arch), opus_fft_alloc_arm_neon(_st))
#define opus_fft_free_arch(_st, arch) \
((void)(arch), opus_fft_free_arm_neon(_st))
#define opus_fft(_st, _fin, _fout, arch) \
((void)(arch), opus_fft_neon(_st, _fin, _fout))
#define opus_ifft(_st, _fin, _fout, arch) \
((void)(arch), opus_ifft_neon(_st, _fin, _fout))
#endif /* OPUS_HAVE_RTCD */
#endif /* HAVE_ARM_NE10 */
#endif

35
local_plugins/opus/android/libs/opus/celt/arm/fixed_arm64.h

@ -1,35 +0,0 @@
/* Copyright (C) 2015 Vidyo */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FIXED_ARM64_H
#define FIXED_ARM64_H
#include <arm_neon.h>
#undef SIG2WORD16
#define SIG2WORD16(x) (vqmovns_s32(PSHR32((x), SIG_SHIFT)))
#endif

80
local_plugins/opus/android/libs/opus/celt/arm/fixed_armv4.h

@ -1,80 +0,0 @@
/* Copyright (C) 2013 Xiph.Org Foundation and contributors */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FIXED_ARMv4_H
#define FIXED_ARMv4_H
/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */
#undef MULT16_32_Q16
static OPUS_INLINE opus_val32 MULT16_32_Q16_armv4(opus_val16 a, opus_val32 b)
{
unsigned rd_lo;
int rd_hi;
__asm__(
"#MULT16_32_Q16\n\t"
"smull %0, %1, %2, %3\n\t"
: "=&r"(rd_lo), "=&r"(rd_hi)
: "%r"(b),"r"(SHL32(a,16))
);
return rd_hi;
}
#define MULT16_32_Q16(a, b) (MULT16_32_Q16_armv4(a, b))
/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */
#undef MULT16_32_Q15
static OPUS_INLINE opus_val32 MULT16_32_Q15_armv4(opus_val16 a, opus_val32 b)
{
unsigned rd_lo;
int rd_hi;
__asm__(
"#MULT16_32_Q15\n\t"
"smull %0, %1, %2, %3\n\t"
: "=&r"(rd_lo), "=&r"(rd_hi)
: "%r"(b), "r"(SHL32(a,16))
);
/*We intentionally don't OR in the high bit of rd_lo for speed.*/
return SHL32(rd_hi,1);
}
#define MULT16_32_Q15(a, b) (MULT16_32_Q15_armv4(a, b))
/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add.
b must fit in 31 bits.
Result fits in 32 bits. */
#undef MAC16_32_Q15
#define MAC16_32_Q15(c, a, b) ADD32(c, MULT16_32_Q15(a, b))
/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add.
Result fits in 32 bits. */
#undef MAC16_32_Q16
#define MAC16_32_Q16(c, a, b) ADD32(c, MULT16_32_Q16(a, b))
/** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */
#undef MULT32_32_Q31
#define MULT32_32_Q31(a,b) (opus_val32)((((opus_int64)(a)) * ((opus_int64)(b)))>>31)
#endif

151
local_plugins/opus/android/libs/opus/celt/arm/fixed_armv5e.h

@ -1,151 +0,0 @@
/* Copyright (C) 2007-2009 Xiph.Org Foundation
Copyright (C) 2003-2008 Jean-Marc Valin
Copyright (C) 2007-2008 CSIRO
Copyright (C) 2013 Parrot */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FIXED_ARMv5E_H
#define FIXED_ARMv5E_H
#include "fixed_armv4.h"
/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */
#undef MULT16_32_Q16
static OPUS_INLINE opus_val32 MULT16_32_Q16_armv5e(opus_val16 a, opus_val32 b)
{
int res;
__asm__(
"#MULT16_32_Q16\n\t"
"smulwb %0, %1, %2\n\t"
: "=r"(res)
: "r"(b),"r"(a)
);
return res;
}
#define MULT16_32_Q16(a, b) (MULT16_32_Q16_armv5e(a, b))
/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */
#undef MULT16_32_Q15
static OPUS_INLINE opus_val32 MULT16_32_Q15_armv5e(opus_val16 a, opus_val32 b)
{
int res;
__asm__(
"#MULT16_32_Q15\n\t"
"smulwb %0, %1, %2\n\t"
: "=r"(res)
: "r"(b), "r"(a)
);
return SHL32(res,1);
}
#define MULT16_32_Q15(a, b) (MULT16_32_Q15_armv5e(a, b))
/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add.
b must fit in 31 bits.
Result fits in 32 bits. */
#undef MAC16_32_Q15
static OPUS_INLINE opus_val32 MAC16_32_Q15_armv5e(opus_val32 c, opus_val16 a,
opus_val32 b)
{
int res;
__asm__(
"#MAC16_32_Q15\n\t"
"smlawb %0, %1, %2, %3;\n"
: "=r"(res)
: "r"(SHL32(b,1)), "r"(a), "r"(c)
);
return res;
}
#define MAC16_32_Q15(c, a, b) (MAC16_32_Q15_armv5e(c, a, b))
/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add.
Result fits in 32 bits. */
#undef MAC16_32_Q16
static OPUS_INLINE opus_val32 MAC16_32_Q16_armv5e(opus_val32 c, opus_val16 a,
opus_val32 b)
{
int res;
__asm__(
"#MAC16_32_Q16\n\t"
"smlawb %0, %1, %2, %3;\n"
: "=r"(res)
: "r"(b), "r"(a), "r"(c)
);
return res;
}
#define MAC16_32_Q16(c, a, b) (MAC16_32_Q16_armv5e(c, a, b))
/** 16x16 multiply-add where the result fits in 32 bits */
#undef MAC16_16
static OPUS_INLINE opus_val32 MAC16_16_armv5e(opus_val32 c, opus_val16 a,
opus_val16 b)
{
int res;
__asm__(
"#MAC16_16\n\t"
"smlabb %0, %1, %2, %3;\n"
: "=r"(res)
: "r"(a), "r"(b), "r"(c)
);
return res;
}
#define MAC16_16(c, a, b) (MAC16_16_armv5e(c, a, b))
/** 16x16 multiplication where the result fits in 32 bits */
#undef MULT16_16
static OPUS_INLINE opus_val32 MULT16_16_armv5e(opus_val16 a, opus_val16 b)
{
int res;
__asm__(
"#MULT16_16\n\t"
"smulbb %0, %1, %2;\n"
: "=r"(res)
: "r"(a), "r"(b)
);
return res;
}
#define MULT16_16(a, b) (MULT16_16_armv5e(a, b))
#ifdef OPUS_ARM_INLINE_MEDIA
#undef SIG2WORD16
static OPUS_INLINE opus_val16 SIG2WORD16_armv6(opus_val32 x)
{
celt_sig res;
__asm__(
"#SIG2WORD16\n\t"
"ssat %0, #16, %1, ASR #12\n\t"
: "=r"(res)
: "r"(x+2048)
);
return EXTRACT16(res);
}
#define SIG2WORD16(x) (SIG2WORD16_armv6(x))
#endif /* OPUS_ARM_INLINE_MEDIA */
#endif

121
local_plugins/opus/android/libs/opus/celt/arm/kiss_fft_armv4.h

@ -1,121 +0,0 @@
/*Copyright (c) 2013, Xiph.Org Foundation and contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.*/
#ifndef KISS_FFT_ARMv4_H
#define KISS_FFT_ARMv4_H
#if !defined(KISS_FFT_GUTS_H)
#error "This file should only be included from _kiss_fft_guts.h"
#endif
#ifdef FIXED_POINT
#undef C_MUL
#define C_MUL(m,a,b) \
do{ \
int br__; \
int bi__; \
int tt__; \
__asm__ __volatile__( \
"#C_MUL\n\t" \
"ldrsh %[br], [%[bp], #0]\n\t" \
"ldm %[ap], {r0,r1}\n\t" \
"ldrsh %[bi], [%[bp], #2]\n\t" \
"smull %[tt], %[mi], r1, %[br]\n\t" \
"smlal %[tt], %[mi], r0, %[bi]\n\t" \
"rsb %[bi], %[bi], #0\n\t" \
"smull %[br], %[mr], r0, %[br]\n\t" \
"mov %[tt], %[tt], lsr #15\n\t" \
"smlal %[br], %[mr], r1, %[bi]\n\t" \
"orr %[mi], %[tt], %[mi], lsl #17\n\t" \
"mov %[br], %[br], lsr #15\n\t" \
"orr %[mr], %[br], %[mr], lsl #17\n\t" \
: [mr]"=r"((m).r), [mi]"=r"((m).i), \
[br]"=&r"(br__), [bi]"=r"(bi__), [tt]"=r"(tt__) \
: [ap]"r"(&(a)), [bp]"r"(&(b)) \
: "r0", "r1" \
); \
} \
while(0)
#undef C_MUL4
#define C_MUL4(m,a,b) \
do{ \
int br__; \
int bi__; \
int tt__; \
__asm__ __volatile__( \
"#C_MUL4\n\t" \
"ldrsh %[br], [%[bp], #0]\n\t" \
"ldm %[ap], {r0,r1}\n\t" \
"ldrsh %[bi], [%[bp], #2]\n\t" \
"smull %[tt], %[mi], r1, %[br]\n\t" \
"smlal %[tt], %[mi], r0, %[bi]\n\t" \
"rsb %[bi], %[bi], #0\n\t" \
"smull %[br], %[mr], r0, %[br]\n\t" \
"mov %[tt], %[tt], lsr #17\n\t" \
"smlal %[br], %[mr], r1, %[bi]\n\t" \
"orr %[mi], %[tt], %[mi], lsl #15\n\t" \
"mov %[br], %[br], lsr #17\n\t" \
"orr %[mr], %[br], %[mr], lsl #15\n\t" \
: [mr]"=r"((m).r), [mi]"=r"((m).i), \
[br]"=&r"(br__), [bi]"=r"(bi__), [tt]"=r"(tt__) \
: [ap]"r"(&(a)), [bp]"r"(&(b)) \
: "r0", "r1" \
); \
} \
while(0)
#undef C_MULC
#define C_MULC(m,a,b) \
do{ \
int br__; \
int bi__; \
int tt__; \
__asm__ __volatile__( \
"#C_MULC\n\t" \
"ldrsh %[br], [%[bp], #0]\n\t" \
"ldm %[ap], {r0,r1}\n\t" \
"ldrsh %[bi], [%[bp], #2]\n\t" \
"smull %[tt], %[mr], r0, %[br]\n\t" \
"smlal %[tt], %[mr], r1, %[bi]\n\t" \
"rsb %[bi], %[bi], #0\n\t" \
"smull %[br], %[mi], r1, %[br]\n\t" \
"mov %[tt], %[tt], lsr #15\n\t" \
"smlal %[br], %[mi], r0, %[bi]\n\t" \
"orr %[mr], %[tt], %[mr], lsl #17\n\t" \
"mov %[br], %[br], lsr #15\n\t" \
"orr %[mi], %[br], %[mi], lsl #17\n\t" \
: [mr]"=r"((m).r), [mi]"=r"((m).i), \
[br]"=&r"(br__), [bi]"=r"(bi__), [tt]"=r"(tt__) \
: [ap]"r"(&(a)), [bp]"r"(&(b)) \
: "r0", "r1" \
); \
} \
while(0)
#endif /* FIXED_POINT */
#endif /* KISS_FFT_ARMv4_H */

118
local_plugins/opus/android/libs/opus/celt/arm/kiss_fft_armv5e.h

@ -1,118 +0,0 @@
/*Copyright (c) 2013, Xiph.Org Foundation and contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.*/
#ifndef KISS_FFT_ARMv5E_H
#define KISS_FFT_ARMv5E_H
#if !defined(KISS_FFT_GUTS_H)
#error "This file should only be included from _kiss_fft_guts.h"
#endif
#ifdef FIXED_POINT
#if defined(__thumb__)||defined(__thumb2__)
#define LDRD_CONS "Q"
#else
#define LDRD_CONS "Uq"
#endif
#undef C_MUL
#define C_MUL(m,a,b) \
do{ \
int mr1__; \
int mr2__; \
int mi__; \
long long aval__; \
int bval__; \
__asm__( \
"#C_MUL\n\t" \
"ldrd %[aval], %H[aval], %[ap]\n\t" \
"ldr %[bval], %[bp]\n\t" \
"smulwb %[mi], %H[aval], %[bval]\n\t" \
"smulwb %[mr1], %[aval], %[bval]\n\t" \
"smulwt %[mr2], %H[aval], %[bval]\n\t" \
"smlawt %[mi], %[aval], %[bval], %[mi]\n\t" \
: [mr1]"=r"(mr1__), [mr2]"=r"(mr2__), [mi]"=r"(mi__), \
[aval]"=&r"(aval__), [bval]"=r"(bval__) \
: [ap]LDRD_CONS(a), [bp]"m"(b) \
); \
(m).r = SHL32(SUB32(mr1__, mr2__), 1); \
(m).i = SHL32(mi__, 1); \
} \
while(0)
#undef C_MUL4
#define C_MUL4(m,a,b) \
do{ \
int mr1__; \
int mr2__; \
int mi__; \
long long aval__; \
int bval__; \
__asm__( \
"#C_MUL4\n\t" \
"ldrd %[aval], %H[aval], %[ap]\n\t" \
"ldr %[bval], %[bp]\n\t" \
"smulwb %[mi], %H[aval], %[bval]\n\t" \
"smulwb %[mr1], %[aval], %[bval]\n\t" \
"smulwt %[mr2], %H[aval], %[bval]\n\t" \
"smlawt %[mi], %[aval], %[bval], %[mi]\n\t" \
: [mr1]"=r"(mr1__), [mr2]"=r"(mr2__), [mi]"=r"(mi__), \
[aval]"=&r"(aval__), [bval]"=r"(bval__) \
: [ap]LDRD_CONS(a), [bp]"m"(b) \
); \
(m).r = SHR32(SUB32(mr1__, mr2__), 1); \
(m).i = SHR32(mi__, 1); \
} \
while(0)
#undef C_MULC
#define C_MULC(m,a,b) \
do{ \
int mr__; \
int mi1__; \
int mi2__; \
long long aval__; \
int bval__; \
__asm__( \
"#C_MULC\n\t" \
"ldrd %[aval], %H[aval], %[ap]\n\t" \
"ldr %[bval], %[bp]\n\t" \
"smulwb %[mr], %[aval], %[bval]\n\t" \
"smulwb %[mi1], %H[aval], %[bval]\n\t" \
"smulwt %[mi2], %[aval], %[bval]\n\t" \
"smlawt %[mr], %H[aval], %[bval], %[mr]\n\t" \
: [mr]"=r"(mr__), [mi1]"=r"(mi1__), [mi2]"=r"(mi2__), \
[aval]"=&r"(aval__), [bval]"=r"(bval__) \
: [ap]LDRD_CONS(a), [bp]"m"(b) \
); \
(m).r = SHL32(mr__, 1); \
(m).i = SHL32(SUB32(mi1__, mi2__), 1); \
} \
while(0)
#endif /* FIXED_POINT */
#endif /* KISS_FFT_GUTS_H */

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save