Browse Source

Merge branch 'new_dev' of github.com:deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
lxm 1 year ago
parent
commit
cfbb1ccbb3
  1. 1
      .gitignore
  2. 25
      .metadata
  3. 27
      SpeexKit.xcframework/Info.plist
  4. 44
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusTools.h
  5. 100
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusUnit.h
  6. 21
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexKit.h
  7. 40
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexUnit.h
  8. BIN
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist
  9. 6
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap
  10. BIN
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit
  11. 8
      ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  12. 8
      ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  13. 238
      lib/core/translations/language/en_us.dart
  14. 208
      lib/core/translations/language/zh_cn.dart
  15. 8
      lib/core/utils/permission_util.dart
  16. 6
      lib/modules/agent/controllers/agent_controller.dart
  17. 4
      lib/modules/explore/views/explore_view.dart
  18. 23
      lib/modules/home/controllers/home_controller.dart
  19. 153
      lib/modules/home/views/home_view.dart
  20. 15
      lib/modules/login/controllers/login_controller.dart
  21. 30
      lib/modules/meeting/controllers/meeting_connect_controller.dart
  22. 28
      lib/modules/meeting/controllers/meeting_home_controller.dart
  23. 12
      lib/modules/meeting/controllers/meeting_record_controller.dart
  24. 5
      lib/modules/meeting/controllers/mind_map_controller.dart
  25. 12
      lib/modules/meeting/views/bottomSheet/ai_bottom_sheet.dart
  26. 10
      lib/modules/meeting/views/bottomSheet/filtrate_drawer.dart
  27. 14
      lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart
  28. 19
      lib/modules/meeting/views/bottomSheet/member_bottom_sheet.dart
  29. 7
      lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart
  30. 13
      lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart
  31. 24
      lib/modules/meeting/views/bottomSheet/record_language_bottom_sheet.dart
  32. 2
      lib/modules/meeting/views/bottomSheet/second_level_bottom_sheet.dart
  33. 41
      lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart
  34. 13
      lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart
  35. 15
      lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart
  36. 4
      lib/modules/meeting/views/meeting_connect_view.dart
  37. 20
      lib/modules/meeting/views/meeting_details_view.dart
  38. 10
      lib/modules/meeting/views/meeting_home_view.dart
  39. 34
      lib/modules/meeting/views/meeting_mine_view.dart
  40. 2
      lib/modules/meeting/views/meeting_record_view.dart
  41. 6
      lib/modules/meeting/views/meeting_view.dart
  42. 9
      lib/modules/meeting/views/mine/meeting_cloud_view.dart
  43. 86
      lib/modules/meeting/views/mine/meeting_member_view.dart
  44. 2
      lib/modules/meeting/views/mine/meeting_transcription_view.dart
  45. 8
      lib/modules/meeting/views/tabs/overview_tab.dart
  46. 6
      lib/modules/meeting/views/tabs/speech_tab.dart
  47. 24
      lib/modules/meeting/views/tabs/summary_tab.dart
  48. 6
      lib/modules/meeting/views/template/template_view.dart
  49. 25
      lib/modules/permissions/controllers/permissions_controller.dart
  50. 42
      lib/modules/settings/views/settings_view.dart
  51. 7
      lib/modules/splash/controllers/splash_controller.dart
  52. 6
      lib/modules/translation/models/translation_models.dart
  53. 12
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  54. 2
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  55. 5
      local_plugins/azure_speech/ios/azure_speech/Package.swift
  56. 153
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift
  57. 582
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  58. 80
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift
  59. 12
      local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift
  60. 27
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/Info.plist
  61. 24
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitKit.h
  62. 58
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitPlayer.h
  63. 66
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusDecoder.h
  64. 62
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusEncoder.h
  65. 53
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusFormat.h
  66. 55
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWav.h
  67. 51
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWtg.h
  68. 54
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLSpeexDecoder.h
  69. BIN
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Info.plist
  70. BIN
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/JLAudioUnitKit
  71. 6
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Modules/module.modulemap
  72. 52
      local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/readme.txt
  73. 15
      local_plugins/ble_service/ios/ble_service/Package.swift
  74. 7
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift
  75. 278
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
  76. 135
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/RecordingFile.swift
  77. 4
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
  78. 374
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift
  79. 85
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/opuse.swift
  80. 43
      local_plugins/ble_service/ios/ble_service/opus.xcframework/Info.plist
  81. 981
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus.h
  82. 753
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_defines.h
  83. 660
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_multistream.h
  84. 159
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_types.h
  85. BIN
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Info.plist
  86. 6
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Modules/module.modulemap
  87. BIN
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/opus
  88. 981
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus.h
  89. 753
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_defines.h
  90. 660
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_multistream.h
  91. 159
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_types.h
  92. BIN
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Info.plist
  93. 6
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Modules/module.modulemap
  94. BIN
      local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/opus
  95. 136
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt
  96. 37
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt
  97. 17
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt
  98. 2
      pubspec.yaml

1
.gitignore

@ -222,3 +222,4 @@ fastlane/readme.md
.trae/rules/project_rules.md .trae/rules/project_rules.md
.augment/rules/imported/project_rules.md .augment/rules/imported/project_rules.md
.kiro/ .kiro/
ios/Runner.xcodeproj/project.pbxproj

25
.metadata

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "5c1433509f997e9059d230942bdf83f1b88a179e" revision: "8defaa71a77c16e8547abdbfad2053ce3a6e2d5b"
channel: "stable" channel: "stable"
project_type: app project_type: app
@ -13,11 +13,26 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
- platform: android - platform: android
create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
- platform: ios
create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
- platform: linux
create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
- platform: macos
create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
- platform: web
create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
- platform: windows
create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b
# User provided section # User provided section

27
SpeexKit.xcframework/Info.plist

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>SpeexKit.framework/SpeexKit</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>SpeexKit.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

44
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusTools.h

@ -0,0 +1,44 @@
//
// OpusTools.h
// SpeexKit
//
// Created by EzioChan on 2024/1/17.
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol OpusToolsDelegate <NSObject>
-(void)opusToolsPcmData:(NSData*)data;
-(void)opusToolsPcmFile:(NSData *)data;
-(void)opusToolsOpusData:(NSData *)data;
-(void)opusToolsOpusFile:(NSData *)data;
@end
@interface OpusTools : NSObject
@property(nonatomic,weak)id<OpusToolsDelegate> delegate;
-(void)opusDecodeInfo:(int)sampleRate Channel:(int)channel frameSize:(int) fs isHasHead:(BOOL)head;
-(void)opusEncodeInfo:(int)sampleRate Channel:(int)channel frameSize:(int)fs isHasHead:(BOOL)head;
-(void)decodeData:(NSData*)data;
-(void)decodeFile:(NSString*)path;
-(void)encodeData:(NSData*)data;
-(void)encodeFile:(NSString*)path;
@end
NS_ASSUME_NONNULL_END

100
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusUnit.h

@ -0,0 +1,100 @@
//
// 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 frameRate 帧长度
* 16k :40
* 32K :80
* 64k :160
* 96k :320
* 128k:640
* @param channel 声道 例如:1、2 (默认 1)
* @param isHeader 是否带头
*/
+(void)opusSetSampleRate:(int)rate frameRate:(int)frameRate Channels:(int)channel isHeader:(BOOL)isHeader;
#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)opusEncoderRun;
/**
* 输入pcm的数据
* 通知监听“kOPUS_DECODE_DATA”获得解码后数据
* 通知数据类型:NSNotification.object => NSData(opus格式)
* @param data opus数据流(长度:1024)
*/
+ (void)pcmWriteData:(NSData*)data;
/**
* 流式编码【关闭】
*/
+ (int)opusEncoderStop;
@end
NS_ASSUME_NONNULL_END

21
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexKit.h

@ -0,0 +1,21 @@
//
// 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>
#import <SpeexKit/OpusTools.h>

40
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexUnit.h

@ -0,0 +1,40 @@
//
// 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
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist

Binary file not shown.

6
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap

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

BIN
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit

Binary file not shown.

8
ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

8
ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

238
lib/core/translations/language/en_us.dart

@ -208,6 +208,233 @@ const Map<String, String> enUS = {
'aiSummary': 'AI Summary', 'aiSummary': 'AI Summary',
'meetingTranscript': 'Meeting Transcript', 'meetingTranscript': 'Meeting Transcript',
// Meeting模块 - 导航和基础功能
'files': 'Files',
'mine': 'Mine',
'connect': 'Connect',
'allFiles': 'All Files',
'searchInAllFiles': 'Search in all files',
'from': 'From',
'sortBy': 'Sort by',
'creationTime': 'Creation Time',
'bluetoothConnection': 'Bluetooth Connection',
'meetingReconnect': 'Reconnect',
'searching': 'Searching...',
'searchFailed': 'Search failed, please check if Bluetooth is enabled',
'aiPenNotFound': 'AI PEN device not found',
'connectedToAiPen': 'Connected to AI PEN device',
'checkingNewFiles': 'Checking for new files to import',
'startingWifi': 'Starting Wi-Fi...',
'newFilesDetected': 'New files detected, preparing to import',
'noNewFiles': 'No new files to import',
'connectingWifi': 'Connecting to Wi-Fi...',
'wifiConnected': 'Wi-Fi connected successfully',
'downloadingFiles': 'Downloading files...',
'downloadingFileProgress': 'Total {total} files, downloading file {current}',
'downloadComplete': 'Download complete',
'downloadCompleteInfo':
'Download complete, {count} files in total, returning automatically in 2 seconds',
// Meeting模块 - 录音功能
'startRecording': 'Start Recording',
'liveRecording': 'Live Recording',
'audioVideoRecording': 'Audio/Video Recording',
'callRecording': 'Call Recording',
'importAudio': 'Import Audio',
'transcribing': 'Transcribing...',
'summarizing': 'Summarizing...',
'analyzing': 'Analyzing...',
'summaryComplete': 'Summary Complete',
//'generating': 'Generating...',
'generate': 'Generate',
'overview': 'Overview',
'transcription': 'Transcription',
'summary': 'Summary',
'mindMap': 'Mind Map',
'editSpeaker': 'Edit',
'nameSpeaker': 'Name Speaker',
// Meeting模块 - 我的页面
'transcriptionRecords': 'Transcription Records',
'templateCommunity': 'Template Community',
'comingSoon': 'Coming Soon',
'integration': 'Integration',
'voitransPrivateCloud': 'VOITRANS PRIVATE CLOUD',
'enabled': 'Enabled',
'disabled': 'Disabled',
'myVoitransDevice': 'My VOITRANS Device',
'helpAndFeedback': 'Help & Feedback',
'aboutVoitrans': 'About VOITRANS',
'meetingDays': 'Days',
'meetingHours': 'Hours',
'voitransAiMember': 'Voitrans AI Member',
'unlockStarterMember': 'Unlock Starter Member',
'bindDeviceToActivate': 'Bind Voitrans device to activate Starter membership',
'unlockNow': 'Unlock Now',
// Meeting模块 - 会员功能
'comparison': 'Comparison',
'detailedInfo': 'Detailed Information',
'monthlyTranscriptionTime': 'Monthly Transcription Time',
'unlimited': 'Unlimited',
'professionalTemplates': 'Professional Templates',
'limited': 'Limited',
'fullTemplates': 'Full Templates',
'professionalTerms': 'Professional Terms',
'customTemplates': 'Custom Templates',
'visualMindMap': 'Visual Mind Map',
'speakerIdentification': 'Speaker Identification',
'aiVoiceEnhancement': 'AI Voice Enhancement',
'audioImport': 'Audio Import',
'multipleExportFormats': 'Multiple Export Formats',
'professionalSummaryTemplates': 'Professional Summary Templates',
'professionalTemplateDesc':
'Professional template design to improve your work efficiency. Customized according to industry needs.',
'customTemplateDesc':
'You can customize summary prompts according to your needs and use them to generate the required summaries.',
'askAiDesc':
'Intelligently extract deeper information from recordings, generate data tables, key discussion conclusions, To-Do lists, report emails and other formatted content with one click.',
'visualMindMapDesc':
'Beyond summaries - organize and visualize key points with mind maps for more effective understanding of the full text.',
'templateCommunityDesc':
'Discover, share, and get inspiration from the template library created by users.',
'speakerIdentificationDesc':
'Audio transcription will support speaker identification, more intuitively restore meeting or phone call content. At the same time, the summary will be more accurate.',
'privateCloudSyncDesc':
'Your recordings and transcriptions will be securely stored on dedicated cloud servers, with full encryption during upload and download to ensure privacy and security.',
'aiVoiceEnhancementDesc':
'AI voice enhancement supports multiple noise reduction settings, intelligently eliminates background noise, enhances voice clarity, and improves audio playback quality.',
'audioImportDesc':
'Easily import audio files from local or third-party applications, such as voice memos, Google Drive, etc.',
'multipleExportFormatsDesc':
'Export your audio recordings, transcription text and summaries in various formats (TXT, PDF, JPEG) to meet your needs.',
// Meeting模块 - 升级会员
'upgradeMembership': 'Upgrade Your Membership',
'yearlySubscription': 'Yearly Subscription',
'monthlySubscription': 'Monthly Subscription',
'proMonthly': 'Pro ¥60/month',
'proMonthlyDesc':
'7-day free trial, then ¥800/year. Cancel anytime before trial ends.',
'sevenDayTrial': '7-day Free Trial',
'unlimitedMonthly': 'Unlimited ¥120/month',
'unlimitedMonthlyDesc':
'7-day free trial, then ¥1600/year. Cancel anytime before trial ends.',
'trialNote': '*Free trial for 7 days, then charged annually, cancel anytime.',
'proPrice': 'Pro ¥120',
'proMonthlyPrice': '¥120/month',
'subscribe': 'Subscribe',
'unlimitedPrice': 'Unlimited ¥198',
'unlimitedMonthlyPrice': '¥198/month',
'upgradeNote':
'*After upgrading subscription, the remaining days of the original subscription will be automatically calculated and refunded to your payment account',
'buyMoreTranscriptionTime': 'Buy More Transcription Time',
'sixThousandMinutes': '6,000 minutes',
'threeThousandMinutes': '3,000 minutes',
'sixHundredMinutes': '600 minutes',
'oneHundredTwentyMinutes': '120 minutes',
'validityPeriod': '*Validity Period: ',
'validityNote':
'Activated immediately after purchase, valid for 2 years after activation.',
'purchase': 'Purchase',
// Meeting模块 - 模版和生成
'selectSummaryTemplate': 'Select Summary Template',
'useThisTemplate': 'Use This Template',
'lastUsed': 'Last Used',
'viewAll': 'View All',
'recordingLanguage': 'Recording Language',
'chineseSimplified': 'Chinese Mandarin (Simplified)',
'meetingGenerateNow': 'Generate Now',
'keyEventsAnalyzed': 'Analyzed {count} key events.',
'aiAnalysisDesc':
'AI analysis identifies key events in recordings, allowing you to select important events for AI generation.',
// Meeting模块 - 文件操作和状态
'importing': 'Importing...',
'batchImporting': 'Batch importing...',
'importingLocalRecordings': 'Importing local recordings...',
'importFailed': 'Import file failed: {filePath}, error: {error}',
'importSuccess': 'Successfully imported {count} local recordings',
'importError': 'Import failed: {error}',
'pleaseSelectAudioFile': 'Please select audio file',
'shareAudio': 'Share Meeting Audio',
'shareComplete': 'Share complete!',
'shareFailed': 'Share failed: {error}',
'fileProcessingFailed': 'File processing failed: {filePath}, error: {error}',
// 翻译模块 - Meeting相关
'meetingUnknownLanguage': 'Unknown Language',
// 权限相关 - Meeting相关
'meetingStoragePermission': 'Storage',
'meetingStoragePermissionExplanation':
'We need access to storage permissions so you can select and upload audio files.',
'meetingStoragePermissionPermanentDenial':
'Detected that you have permanently denied storage permission. To continue using this feature, please go to app settings to manually enable storage permission.',
// Meeting模块 - 思维导图相关
'shareMindMap': 'Share Mind Map',
'shareSuccess': 'Share Success',
// Meeting模块 - AI相关
'askAI': 'Ask AI ...',
'askAIPrompt': 'What can I help you with today?',
'continueAsk': 'Continue Asking',
'todoList': 'To-Do List',
'extractConclusion': 'Extract Conclusion',
'generateKeyIndicators': 'Generate Key Indicators',
// Meeting模块 - 更多操作相关
'reSummarize': 'Re-summarize',
// Meeting模块 - 录音语言相关
'recordLanguage': 'Recording Language',
// 'chinese': 'Chinese Mandarin (Simplified)',
'cantonese': 'Cantonese',
'sichuan': 'Sichuan Dialect',
'shanghai': 'Shanghai Dialect',
// 'english': 'English',
// 'japanese': 'Japanese',
// 'korean': 'Korean',
// 'french': 'French',
// 'spanish': 'Spanish',
// 'portuguese': 'Portuguese',
// 'indonesian': 'Indonesian',
// Meeting模块 - 分享相关
'shareLink': 'Share Link',
'copyTranscript': 'Copy Transcript',
'copySummary': 'Copy Summary',
'exportAudio': 'Export Audio',
'exportTranscript': 'Export Transcript',
'shareMeetingAudio': 'Share Meeting Audio',
'exportSummary': 'Export Summary',
'exportAs': 'Export As',
'exportingPDF': 'Exporting PDF...',
// Meeting模块 - 发言者相关
'nameCurrentSpeaker': 'Name Current Speaker',
'recentlyUsedNames': 'Recently Used Names',
'applyToCurrentParagraph': 'Apply to Current Paragraph',
'totalTime': 'Total Time',
'summarySpeakerWillBeUpdatedAutomatically':
'Summary speakers will be updated automatically',
// Meeting模块 - 云端同步相关
'syncToCloud': 'Sync to Cloud',
'syncToCloudTip':
'Automatically sync your recordings to the cloud, you can play, manage and share recordings through any VOITRANS APP or VOITRANS Web (web.voitrans.com).',
'syncToVoitransApp': 'Sync VOITRANS APP',
'transcriptionRecord': 'Transcription Record',
// Meeting模块 - 总结页面相关
'dateAndTime': 'Date & Time',
'location': 'Location',
'attendees': 'Attendees',
'inputLocation': 'Enter Location',
'inputAttendees': 'Enter Attendees',
// 设置页 // 设置页
"systemSettings": "System Settings", "systemSettings": "System Settings",
"premiumMember": "Premium Membership", "premiumMember": "Premium Membership",
@ -305,7 +532,7 @@ const Map<String, String> enUS = {
"loadFailedClickRetry": "Load failed, click to retry", "loadFailedClickRetry": "Load failed, click to retry",
"swipeUpForMore": "Swipe up for more", "swipeUpForMore": "Swipe up for more",
// Preset prompts // 预设提示词
"presetMusic": "Play some music", // Play some music "presetMusic": "Play some music", // Play some music
"presetNavigation": "Navigation to", // Navigation "presetNavigation": "Navigation to", // Navigation
"presetWeather": "Today's weather", // Weather "presetWeather": "Today's weather", // Weather
@ -758,7 +985,7 @@ const Map<String, String> enUS = {
"renameAudioFile": "Rename Audio File", "renameAudioFile": "Rename Audio File",
"enterNewFileName": "Please enter new file name", "enterNewFileName": "Please enter new file name",
// 翻译详情页 // 翻译详情页(translation_detail_view.dart)
"originalRecord": "Original Record", "originalRecord": "Original Record",
"recordSummary": "Record Summary", "recordSummary": "Record Summary",
"clickToGenerateSummary": "clickToGenerateSummary":
@ -955,11 +1182,4 @@ const Map<String, String> enUS = {
"justNow": "Just now", "justNow": "Just now",
"minutesAgo": "@minutes minutes ago", "minutesAgo": "@minutes minutes ago",
"yesterday": "Yesterday", "yesterday": "Yesterday",
// "monday": "Monday",
// "tuesday": "Tuesday",
// "wednesday": "Wednesday",
// "thursday": "Thursday",
// "friday": "Friday",
// "saturday": "Saturday",
// "sunday": "Sunday",
}; };

208
lib/core/translations/language/zh_cn.dart

@ -202,6 +202,214 @@ const Map<String, String> zhCN = {
'aiSummary': 'AI总结', 'aiSummary': 'AI总结',
'meetingTranscript': '会议原文', 'meetingTranscript': '会议原文',
// Meeting模块 - 导航和基础功能
'files': '文件',
'mine': '我的',
'connect': '连接',
'allFiles': '全部文件',
'searchInAllFiles': '在所有文件中搜索',
'from': '来自',
'sortBy': '排序方式',
'creationTime': '创建时间',
'bluetoothConnection': '蓝牙连接',
'meetingReconnect': '重新连接',
'searching': '搜索中...',
'searchFailed': '搜索失败,请检查蓝牙是否开启',
'aiPenNotFound': '未搜索到 AI PEN 设备',
'connectedToAiPen': '已连接到 AI PEN 设备',
'checkingNewFiles': '检查是否有新文件需要导入',
'startingWifi': '正在开启Wi-Fi...',
'newFilesDetected': '检查到有新文件,正在准备导入',
'noNewFiles': '没有新文件需要导入',
'connectingWifi': '正在连接 Wi-Fi...',
'wifiConnected': 'Wi-Fi 连接成功',
'downloadingFiles': '正在下载文件...',
'downloadingFileProgress': '共{total}个文件,正在下载第{current}个文件',
'downloadComplete': '下载完成',
'downloadCompleteInfo': '下载完成,共{count}个文件,2秒后自动返回',
// Meeting模块 - 录音功能
'startRecording': '开始录音',
'liveRecording': '现场录音',
'audioVideoRecording': '音、视频录音',
'callRecording': '通话录音',
'importAudio': '导入音频',
'transcribing': '正在转写...',
'summarizing': '正在总结...',
'analyzing': '正在分析...',
'summaryComplete': '总结完成',
//'generating': '正在生成...',
'generate': '生成',
'overview': '概览',
'transcription': '转写',
'summary': '总结',
'mindMap': '思维导图',
'editSpeaker': '编辑',
'nameSpeaker': '命名发言者',
// Meeting模块 - 我的页面
'transcriptionRecords': '转写记录',
'templateCommunity': '模版社区',
'comingSoon': '尽请期待',
'integration': '集成',
'voitransPrivateCloud': 'VOITRANS PRIVATE CLOUD',
'enabled': '开启',
'disabled': '关闭',
'myVoitransDevice': '我的VOITRANS设备',
'helpAndFeedback': '帮助与反馈',
'aboutVoitrans': '关于VOITRANS',
'meetingDays': '天',
'meetingHours': '小时',
'voitransAiMember': 'Voitrans AI会员',
'unlockStarterMember': '解锁 Starter 会员',
'bindDeviceToActivate': '绑定 Voitrans 设备可激活 Starter 会员',
'unlockNow': '立即解锁',
// Meeting模块 - 会员功能
'comparison': '对比',
'detailedInfo': '详细信息',
'monthlyTranscriptionTime': '每月转写时长',
'unlimited': '无限',
'professionalTemplates': '专业模版',
'limited': '有限',
'fullTemplates': '全量模版',
'professionalTerms': '专业术语',
'customTemplates': '自定义模版',
'visualMindMap': '可视化思维导图',
'speakerIdentification': '区分说话人',
'aiVoiceEnhancement': 'AI 人声增强',
'audioImport': '音频导入',
'multipleExportFormats': '支持多种导出格式',
'professionalSummaryTemplates': '专业总结模版',
'professionalTemplateDesc': '专业模版设计,提高您的工作效率。根据行业需求量身定制。',
'customTemplateDesc': '您可以根据需求自定义总结 Prompts,并用它们生成所需的总结。',
'askAiDesc': '从录音中智能挖掘更深入信息,一键生成数据表格、关键讨论结论、To Do、汇报邮件等格式内容。',
'visualMindMapDesc': '超越总结-用思维导图整理和可视化要点,更有效地理解全文。',
'templateCommunityDesc': '发现、分享,并从用户创建的模版库中获得灵感。',
'speakerIdentificationDesc': '音频转写将支持区分发言者,更直观的还原会议或电话通话内容。同时总结将更加准确。',
'privateCloudSyncDesc': '您的录音和转写等数据将安全存储在专属的云端服务器中,无论是上传还是下载,全程加密处理,确保隐私安全。',
'aiVoiceEnhancementDesc': 'Al 人声增强支持多档降噪设置,智能消除背景噪音,增强语音清晰度,提升音频回放效果。',
'audioImportDesc': '可以轻松从本地或第三方应用导入音频文件,如语音备忘录、Google云端硬盘等。',
'multipleExportFormatsDesc': '导出您的音频录音、转写文本和摘要为各种格式(TXT、PDF、JPEG),以满足您的需求。',
// Meeting模块 - 升级会员
'upgradeMembership': '升级您的会员',
'yearlySubscription': '年订阅',
'monthlySubscription': '月订阅',
'proMonthly': 'Pro ¥60/月',
'proMonthlyDesc': '7天免费试用,之后按¥800/年。试用期结束前可随时取消。',
'sevenDayTrial': '7天免费试用',
'unlimitedMonthly': 'Unlimited ¥120/月',
'unlimitedMonthlyDesc': '7天免费试用,之后按¥1600/年。试用期结束前可随时取消。',
'trialNote': '*免费试用7天,然后按年收费,随时取消。',
'proPrice': 'Pro ¥120',
'proMonthlyPrice': '¥120/月',
'subscribe': '订阅',
'unlimitedPrice': 'Unlimited ¥198',
'unlimitedMonthlyPrice': '¥198/月',
'upgradeNote': '*升级订阅后,原订阅的剩余天数会自动折算差价并退还至您的支付账户',
'buyMoreTranscriptionTime': '购买更多转写时长',
'sixThousandMinutes': '6,000分钟',
'threeThousandMinutes': '3,000分钟',
'sixHundredMinutes': '600分钟',
'oneHundredTwentyMinutes': '120分钟',
'validityPeriod': '*有效期:',
'validityNote': '购买后立即激活,激活后2年内有效。',
'purchase': '购买',
// Meeting模块 - 模版和生成
'selectSummaryTemplate': '选择总结模版',
'useThisTemplate': '就用这个模版',
'lastUsed': '上次使用',
'viewAll': '查看全部',
'recordingLanguage': '录音语言',
'chineseSimplified': '中文普通话(简体)',
'meetingGenerateNow': '立即生成',
'keyEventsAnalyzed': '分析出{count}个关键事件。',
'aiAnalysisDesc': 'AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。',
// Meeting模块 - 文件操作和状态
'importing': '正在导入...',
'batchImporting': '正在批量导入...',
'importingLocalRecordings': '正在导入本地录音...',
'importFailed': '导入文件失败: {filePath}, error: {error}',
'importSuccess': '成功导入本地 {count} 条录音',
'importError': '导入失败: {error}',
'pleaseSelectAudioFile': '请选择音频文件',
'shareAudio': '分享会议音频',
'shareComplete': '分享完成!',
'shareFailed': '分享失败: {error}',
'fileProcessingFailed': '文件处理失败: {filePath}, 错误: {error}',
// 翻译模块 - Meeting相关
'meetingUnknownLanguage': '未知语言',
// 权限相关 - Meeting相关
'meetingStoragePermission': '存储',
'meetingStoragePermissionExplanation': '我们需要访问存储权限,以便您可以选择并上传音频文件。',
'meetingStoragePermissionPermanentDenial':
'检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。',
// Meeting模块 - 思维导图相关
'shareMindMap': '分享思维导图',
'shareSuccess': '分享成功',
// Meeting模块 - AI相关
'askAI': '问 AI ...',
'askAIPrompt': '今天我能帮您做点什么呢?',
'continueAsk': '继续提问',
'todoList': '代办事项',
'extractConclusion': '提取结论',
'generateKeyIndicators': '生成关键指标',
// Meeting模块 - 更多操作相关
'reSummarize': '重新总结',
// Meeting模块 - 录音语言相关
'recordLanguage': '录音语言',
// 'chinese': '中文普通话(简体)',
'cantonese': '粤语',
'sichuan': '四川话',
'shanghai': '上海话',
// 'english': '英文',
// 'japanese': '日语',
// 'korean': '韩语',
// 'french': '法语',
// 'spanish': '西班牙语',
// 'portuguese': '葡萄牙语',
// 'indonesian': '印尼语',
// Meeting模块 - 分享相关
'shareLink': '分享链接',
'copyTranscript': '复制转写',
'copySummary': '复制总结',
'exportAudio': '导出音频',
'exportTranscript': '导出转写',
'shareMeetingAudio': '分享会议音频',
'exportSummary': '导出总结',
'exportAs': '导出为',
'exportingPDF': '正在导出PDF...',
// Meeting模块 - 发言者相关
'nameCurrentSpeaker': '命名当前发言者',
'recentlyUsedNames': '最近使用过的名称',
'applyToCurrentParagraph': '应用到当前段落',
'totalTime': '总计',
'summarySpeakerWillBeUpdatedAutomatically': '总结的发言者将自动更新',
// Meeting模块 - 云端同步相关
'syncToCloud': '同步到云端',
'syncToCloudTip': '自动将你的录音同步到云端,你可以通过任意VOITRANS APP或VOITRANS Web(web.voitrans.com)播放、管理和分享录音。',
'syncToVoitransApp': '同步VOITRANS APP',
'transcriptionRecord': '转写记录',
// Meeting模块 - 总结页面相关
'dateAndTime': '日期时间',
'location': '地点',
'attendees': '与会人员',
'inputLocation': '输入地点',
'inputAttendees': '输入与会人员',
// 设置页 // 设置页
"systemSettings": "系统设置", "systemSettings": "系统设置",
"premiumMember": "高级会员", "premiumMember": "高级会员",

8
lib/core/utils/permission_util.dart

@ -176,7 +176,9 @@ class PermissionUtil {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'permissionUtilNeed'.tr + permissionName + 'permissionUtilPermission'.tr, 'permissionUtilNeed'.tr +
permissionName +
'permissionUtilPermission'.tr,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
@ -261,7 +263,9 @@ class PermissionUtil {
bool? userChoice = await Get.dialog( bool? userChoice = await Get.dialog(
AlertDialog( AlertDialog(
title: Text('permissionUtilNeed'.tr + permissionName + 'permissionUtilPermission'.tr), title: Text('permissionUtilNeed'.tr +
permissionName +
'permissionUtilPermission'.tr),
content: Text(permanentDenialText), content: Text(permanentDenialText),
actions: [ actions: [
TextButton( TextButton(

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

@ -701,6 +701,12 @@ class AgentController extends GetxController {
final functionName = event.data['name'] ?? ''; final functionName = event.data['name'] ?? '';
final arguments = event.data['arguments'] ?? ''; final arguments = event.data['arguments'] ?? '';
// debugPrint('Fultter 开始调用 mcp: $functionName, $arguments'); // debugPrint('Fultter 开始调用 mcp: $functionName, $arguments');
if (functionName == 'exit_interaction') {
if (isPhoneCallMode.value) {
endPhoneCall(); //退出语音交互,同时退出打电话模式
}
}
break; break;
case AgentServiceEventType.functionCallResult: case AgentServiceEventType.functionCallResult:

4
lib/modules/explore/views/explore_view.dart

@ -23,9 +23,9 @@ class ExploreView extends GetView<ExploreController> {
// 主要内容(可滚动) // 主要内容(可滚动)
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
physics: const BouncingScrollPhysics(), //physics: const BouncingScrollPhysics(),
child: Padding( child: Padding(
padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 80.h), padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 0.h),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [

23
lib/modules/home/controllers/home_controller.dart

@ -103,6 +103,10 @@ class HomeController extends GetxController {
Future<void> refreshDeviceConnection() async { Future<void> refreshDeviceConnection() async {
await _checkConnectionStatus(); await _checkConnectionStatus();
// 如果设备已连接,立即刷新电池信息
if (_isFullyConnected.value) {
await _refreshBatteryInfo();
}
} }
void rescanDevices() { void rescanDevices() {
@ -137,6 +141,16 @@ class HomeController extends GetxController {
if (!wasConnected && isNowConnected) { if (!wasConnected && isNowConnected) {
await _bleManager.fetchDeviceInfo(); await _bleManager.fetchDeviceInfo();
// 连接成功后立即刷新一次电池信息
Future.delayed(const Duration(milliseconds: 500), () {
_refreshBatteryInfo();
});
}
// 根据连接状态控制定时器
if (isNowConnected) {
_startBatteryRefreshTimer(); // 连接时启动定时器
} else {
_stopBatteryRefreshTimer(); // 断开时停止定时器
} }
} }
@ -146,6 +160,7 @@ class HomeController extends GetxController {
if (_isFullyConnected.value) { if (_isFullyConnected.value) {
_isFullyConnected.value = false; _isFullyConnected.value = false;
_isFullyConnected.refresh(); _isFullyConnected.refresh();
_stopBatteryRefreshTimer(); // 发生错误时停止定时器
update(); update();
} }
} }
@ -298,18 +313,18 @@ class HomeController extends GetxController {
return; return;
} }
Logger.d(_tag, '开始刷新电池信息...'); //Logger.d(_tag, '开始刷新电池信息...');
// 通过BLE管理器获取最新的电池信息 // 通过BLE管理器获取最新的电池信息
final success = await _bleManager.fetchBatteryInfo(); final success = await _bleManager.fetchBatteryInfo();
if (success) { if (success) {
Logger.d(_tag, '电池信息刷新请求发送成功'); //Logger.d(_tag, '电池信息刷新请求发送成功');
} else { } else {
Logger.w(_tag, '电池信息刷新请求发送失败'); //Logger.w(_tag, '电池信息刷新请求发送失败');
} }
} catch (e) { } catch (e) {
Logger.e(_tag, '刷新电池信息时发生错误: $e'); //Logger.e(_tag, '刷新电池信息时发生错误: $e');
} }
} }
} }

153
lib/modules/home/views/home_view.dart

@ -76,7 +76,7 @@ class HomeView extends GetView<HomeController> {
return Scaffold( return Scaffold(
backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFC), backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFC),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: Padding(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -84,12 +84,12 @@ class HomeView extends GetView<HomeController> {
// 顶部标题栏 // 顶部标题栏
_buildAppBar(isDarkMode), _buildAppBar(isDarkMode),
// 顶部图片 // 可变高度的顶部图片区域
_buildEarbudsImage(), Obx(() => _buildAdaptiveEarbudsImage()),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// 设备状态卡片 // 设备状态卡片 - 使用 AnimatedContainer 实现平滑过渡
IntroStepBuilder( IntroStepBuilder(
group: 'home', group: 'home',
order: 1, order: 1,
@ -107,7 +107,7 @@ class HomeView extends GetView<HomeController> {
}, },
builder: (BuildContext context, GlobalKey key) => SizedBox( builder: (BuildContext context, GlobalKey key) => SizedBox(
key: key, key: key,
child: _buildDeviceStatusCard(isDarkMode), child: _buildAnimatedDeviceStatusCard(isDarkMode),
), ),
), ),
@ -143,10 +143,17 @@ class HomeView extends GetView<HomeController> {
), ),
), ),
// 热门AI体验 // 热门AI体验 - 使用 Expanded 确保始终在可视范围内
_buildSectionTitle(isDarkMode), Expanded(
SizedBox(height: 16.h), child: Column(
_buildPremiumAILayout(), crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(isDarkMode),
SizedBox(height: 16.h),
Expanded(child: _buildPremiumAILayout()),
],
),
),
], ],
), ),
), ),
@ -196,70 +203,48 @@ class HomeView extends GetView<HomeController> {
); );
} }
// 耳机图片
Widget _buildEarbudsImage() {
// 自适应高度的耳机图片 - 根据设备连接状态动态调整高度
Widget _buildAdaptiveEarbudsImage() {
final bool isDarkMode = Get.isDarkMode; final bool isDarkMode = Get.isDarkMode;
return SizedBox( // 根据设备连接状态调整图片高度
height: 220.h, // 已连接时高度较小,为设备信息让出空间;未连接时高度较大
final double imageHeight = controller.isFullyConnectedRx.value ? 160.h : 220.h;
return AnimatedContainer(
duration: const Duration(milliseconds: 500), // 平滑过渡动画
curve: Curves.easeInOut,
height: imageHeight,
child: PageView( child: PageView(
children: [ children: [
// Center(
// child: ClipRRect(
// // 添加 ClipRRect 来实现圆角
// borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小
// child: SizedBox(
// width: double.infinity,
// height: 200.h,
// child: Image.asset(
// isDarkMode
// ? 'assets/images/robot_light.png'
// : 'assets/images/robot_light.png',
// fit: BoxFit.fitWidth,
// ),
// ),
// ),
// ),
Center( Center(
child: ClipRRect( child: ClipRRect(
// 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
child: Image.asset( child: Image.asset(
isDarkMode isDarkMode
? 'assets/images/headphone_new3.png' ? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png', : 'assets/images/headphone_new4.png',
height: 220.h, height: imageHeight,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
), ),
// Center(
// child: ClipRRect(
// // 添加 ClipRRect
// borderRadius: BorderRadius.circular(16.r),
// child: SizedBox(
// width: double.infinity,
// height: 200.h,
// child: Image.asset(
// isDarkMode
// ? 'assets/images/glasses_light.png'
// : 'assets/images/glasses_light.png',
// fit: BoxFit.fitWidth,
// ),
// ),
// ),
// ),
], ],
), ),
); );
} }
// 设备状态卡片
Widget _buildDeviceStatusCard(bool isDarkMode) {
// 带动画的设备状态卡片 - 实现平滑的高度变化过渡
Widget _buildAnimatedDeviceStatusCard(bool isDarkMode) {
return Obx(() { return Obx(() {
// 使用连接状态作为key,强制在状态改变时重建widget
final isConnected = controller.isFullyConnectedRx.value; final isConnected = controller.isFullyConnectedRx.value;
return Container(
key: ValueKey('device_status_$isConnected'), return AnimatedContainer(
duration: const Duration(milliseconds: 500), // 平滑过渡动画
curve: Curves.easeInOut,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? const Color(0xFF1A1A1A) : Colors.white, color: isDarkMode ? const Color(0xFF1A1A1A) : Colors.white,
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
@ -273,30 +258,58 @@ class HomeView extends GetView<HomeController> {
), ),
padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 16.h), padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 16.h),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, // 重要:让容器高度自适应内容
children: [ children: [
// 状态标题行 // 状态标题行
_buildDeviceStatusHeader(isDarkMode), _buildDeviceStatusHeader(isDarkMode),
// 设备信息 // 设备信息 - 使用 AnimatedSwitcher 实现内容切换动画
SizedBox(height: 12.h), SizedBox(height: 12.h),
Obx(() { AnimatedSwitcher(
// 移除连接失败和连接中的状态判断,直接根据是否完全连接来判断 duration: const Duration(milliseconds: 300),
// 使用响应式访问确保UI更新 transitionBuilder: (Widget child, Animation<double> animation) {
return controller.isFullyConnectedRx.value return FadeTransition(
? _buildConnectedDeviceInfo(isDarkMode) //已连接设备信息 opacity: animation,
: _buildDisconnectedInfo(isDarkMode); //未连接提示 child: SlideTransition(
}), position: Tween<Offset>(
begin: const Offset(0, 0.1),
// 连接按钮(仅在未连接时显示) end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Container(
key: ValueKey('device_info_$isConnected'),
child: isConnected
? _buildConnectedDeviceInfo(isDarkMode)
: _buildDisconnectedInfo(isDarkMode),
),
),
// 连接按钮 - 使用 AnimatedSwitcher 实现按钮切换动画
SizedBox(height: 12.h), SizedBox(height: 12.h),
Obx(() { AnimatedSwitcher(
// 使用响应式访问确保UI更新 duration: const Duration(milliseconds: 300),
if (!controller.isFullyConnectedRx.value) { transitionBuilder: (Widget child, Animation<double> animation) {
return _buildConnectButton(); return FadeTransition(
} else { opacity: animation,
return _buildDeviceButton(isDarkMode); child: SlideTransition(
} position: Tween<Offset>(
}), begin: const Offset(0, 0.1),
end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Container(
key: ValueKey('device_button_$isConnected'),
child: !isConnected
? _buildConnectButton()
: _buildDeviceButton(isDarkMode),
),
),
], ],
), ),
); );

15
lib/modules/login/controllers/login_controller.dart

@ -37,6 +37,7 @@ class LoginController extends GetxController {
String _deviceId = ''; String _deviceId = '';
String _appVersion = ''; // APP版本号 String _appVersion = ''; // APP版本号
String _countryCode = ''; // 国家代码 String _countryCode = ''; // 国家代码
String _languageCode = ''; // 语言代码
UserChannel _userChannel = UserChannel.OFFICIAL; // 默认渠道 UserChannel _userChannel = UserChannel.OFFICIAL; // 默认渠道
final isChinaMainland = false.obs; //是否中国大陆地区 final isChinaMainland = false.obs; //是否中国大陆地区
@ -310,6 +311,10 @@ class LoginController extends GetxController {
_countryCode = Get.locale?.countryCode ?? 'Unknown'; _countryCode = Get.locale?.countryCode ?? 'Unknown';
Logger().d('countryCode: $_countryCode'); Logger().d('countryCode: $_countryCode');
// 获取语言代码
_languageCode = Get.locale?.languageCode ?? 'Unknown';
Logger().d('languageCode: $_languageCode');
// 获取用户渠道(根据平台和设备信息确定) // 获取用户渠道(根据平台和设备信息确定)
_userChannel = versionService.cachedChannel; _userChannel = versionService.cachedChannel;
Logger().d('userChannel: ${_userChannel.value}'); Logger().d('userChannel: ${_userChannel.value}');
@ -317,6 +322,8 @@ class LoginController extends GetxController {
Logger().e('获取APP信息失败: $e'); Logger().e('获取APP信息失败: $e');
_appVersion = 'Unknown'; _appVersion = 'Unknown';
_countryCode = 'Unknown'; _countryCode = 'Unknown';
_languageCode = 'Unknown';
_userChannel = UserChannel.UNKNOWN; _userChannel = UserChannel.UNKNOWN;
} }
} }
@ -790,8 +797,7 @@ class LoginController extends GetxController {
"stype": loginType, "stype": loginType,
"vcode": "", "vcode": "",
"ttoken": "", "ttoken": "",
"phonemodel": "phonemodel": _countryCode + _userChannel.toString(),
_appVersion + _countryCode + _userChannel.toString(),
"phonemac": _deviceId, "phonemac": _deviceId,
"name": "", "name": "",
"avatar": "", "avatar": "",
@ -834,10 +840,13 @@ class LoginController extends GetxController {
"stype": params["stype"], "stype": params["stype"],
"vcode": params["vcode"], "vcode": params["vcode"],
"ttoken": params["ttoken"], "ttoken": params["ttoken"],
"phonemodel": _appVersion + _countryCode + _userChannel.toString(), "phonemodel": _countryCode,
"phonemac": params["phonemac"], "phonemac": params["phonemac"],
"name": params["name"], "name": params["name"],
"avatar": params["avatar"], "avatar": params["avatar"],
"language": _languageCode,
"channel": _userChannel,
"version": _appVersion,
}).timeout( }).timeout(
const Duration(seconds: 30), // 30秒超时 const Duration(seconds: 30), // 30秒超时
onTimeout: () { onTimeout: () {

30
lib/modules/meeting/controllers/meeting_connect_controller.dart

@ -13,7 +13,7 @@ import 'meeting_home_controller.dart';
class MeetingConnectController extends GetxController { class MeetingConnectController extends GetxController {
final _meetingController = Get.find<MeetingHomeController>(); final _meetingController = Get.find<MeetingHomeController>();
RxString stateStr = '搜索中...'.obs; RxString stateStr = 'searching'.tr.obs; // 对应中文:搜索中...
RxBool isRescan = false.obs; RxBool isRescan = false.obs;
RxString importedFilesStr = ''.obs; RxString importedFilesStr = ''.obs;
@ -50,15 +50,15 @@ class MeetingConnectController extends GetxController {
}); });
_isScanningSubscription = FlutterBluePlus.isScanning.listen((state) { _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
if (isScanFailed) { if (isScanFailed) {
stateStr.value = '搜索失败,请检查蓝牙是否开启'; stateStr.value = 'searchFailed'.tr; // 对应中文:搜索失败,请检查蓝牙是否开启
isRescan.value = true; isRescan.value = true;
return; return;
} }
if (!state && _device == null) { if (!state && _device == null) {
stateStr.value = '未搜索到 AI PEN 设备'; stateStr.value = 'aiPenNotFound'.tr; // 对应中文:未搜索到 AI PEN 设备
isRescan.value = true; isRescan.value = true;
} else { } else {
stateStr.value = '搜索中...'; stateStr.value = 'searching'.tr; // 对应中文:搜索中...
isRescan.value = false; isRescan.value = false;
} }
}); });
@ -111,7 +111,7 @@ class MeetingConnectController extends GetxController {
_device = result.device; _device = result.device;
await _stopScan(); await _stopScan();
await _connect(); await _connect();
stateStr.value = '已连接到 AI PEN 设备'; stateStr.value = 'connectedToAiPen'.tr; // 对应中文:已连接到 AI PEN 设备
} }
} }
@ -131,7 +131,7 @@ class MeetingConnectController extends GetxController {
for (var element in characteristics) { for (var element in characteristics) {
if (element.characteristicUuid == Guid('FFF1')) { if (element.characteristicUuid == Guid('FFF1')) {
_writeCharacteristic = element; _writeCharacteristic = element;
stateStr.value = '检查是否有新文件需要导入'; stateStr.value = 'checkingNewFiles'.tr; // 对应中文:检查是否有新文件需要导入
sendData([0x5a, 0x01, 0x0d]); sendData([0x5a, 0x01, 0x0d]);
} else if (element.characteristicUuid == Guid('FFF2')) { } else if (element.characteristicUuid == Guid('FFF2')) {
await element.setNotifyValue(true); await element.setNotifyValue(true);
@ -151,11 +151,11 @@ class MeetingConnectController extends GetxController {
switch (command) { switch (command) {
case 0x0d: // 是否有导入文件 case 0x0d: // 是否有导入文件
if (value[3] == 0x01) { if (value[3] == 0x01) {
stateStr.value = '正在开启Wi-Fi...'; stateStr.value = 'startingWifi'.tr; // 对应中文:正在开启Wi-Fi...
importedFilesStr.value = '检查到有新文件,正在准备导入'; importedFilesStr.value = 'newFilesDetected'.tr; // 对应中文:检查到有新文件,正在准备导入
sendData([0x5a, 0x02, 0x87, 0x01]); sendData([0x5a, 0x02, 0x87, 0x01]);
} else { } else {
importedFilesStr.value = '没有新文件需要导入'; importedFilesStr.value = 'noNewFiles'.tr; // 对应中文:没有新文件需要导入
} }
break; break;
case 0x87: // Wi-Fi 开启/关闭 case 0x87: // Wi-Fi 开启/关闭
@ -193,14 +193,14 @@ class MeetingConnectController extends GetxController {
void _connectWifi() async { void _connectWifi() async {
if (_wifiSSID.isNotEmpty && _wifiPassword.isNotEmpty) { if (_wifiSSID.isNotEmpty && _wifiPassword.isNotEmpty) {
stateStr.value = '正在连接 Wi-Fi...'; stateStr.value = 'connectingWifi'.tr; // 对应中文:正在连接 Wi-Fi...
await WiFiForIoTPlugin.connect( await WiFiForIoTPlugin.connect(
_wifiSSID, _wifiSSID,
password: _wifiPassword, password: _wifiPassword,
security: NetworkSecurity.WPA, security: NetworkSecurity.WPA,
); );
await WiFiForIoTPlugin.forceWifiUsage(true); await WiFiForIoTPlugin.forceWifiUsage(true);
stateStr.value = 'Wi-Fi 连接成功'; stateStr.value = 'wifiConnected'.tr; // 对应中文:Wi-Fi 连接成功
sendData([0x5a, 0x01, 0x03]); sendData([0x5a, 0x01, 0x03]);
} }
} }
@ -220,7 +220,7 @@ class MeetingConnectController extends GetxController {
_ftpConnect?.listCommand = ListCommand.LIST; _ftpConnect?.listCommand = ListCommand.LIST;
List<FTPEntry>? listDirectory = await _ftpConnect?.listDirectoryContent(); List<FTPEntry>? listDirectory = await _ftpConnect?.listDirectoryContent();
if (listDirectory != null && listDirectory.isNotEmpty) { if (listDirectory != null && listDirectory.isNotEmpty) {
stateStr.value = '正在下载文件...'; stateStr.value = 'downloadingFiles'.tr; // 对应中文:正在下载文件...
isDownload.value = true; isDownload.value = true;
int totalSize = listDirectory.fold( int totalSize = listDirectory.fold(
0, 0,
@ -228,15 +228,15 @@ class MeetingConnectController extends GetxController {
); );
for (var i = 0; i < listDirectory.length; i++) { for (var i = 0; i < listDirectory.length; i++) {
importedFilesStr.value = importedFilesStr.value =
'共${listDirectory.length}个文件,正在下载第${i + 1}个文件'; 'downloadingFileProgress'.trParams({'total': '${listDirectory.length}', 'current': '${i + 1}'}); // 对应中文:共{total}个文件,正在下载第{current}个文件
int previousSize = i > 0 ? listDirectory[i - 1].size ?? 0 : 0; int previousSize = i > 0 ? listDirectory[i - 1].size ?? 0 : 0;
FTPEntry element = listDirectory[i]; FTPEntry element = listDirectory[i];
await _downloadFile(totalSize, previousSize, element); await _downloadFile(totalSize, previousSize, element);
await _ftpConnect?.deleteFile(element.name); await _ftpConnect?.deleteFile(element.name);
} }
await _disconnect(); await _disconnect();
stateStr.value = '下载完成'; stateStr.value = 'downloadComplete'.tr; // 对应中文:下载完成
importedFilesStr.value = '下载完成,共${listDirectory.length}个文件,2秒后自动返回'; importedFilesStr.value = 'downloadCompleteInfo'.trParams({'count': '${listDirectory.length}'}); // 对应中文:下载完成,共{count}个文件,2秒后自动返回
Future.delayed(const Duration(seconds: 2), () { Future.delayed(const Duration(seconds: 2), () {
_executeUpload(); _executeUpload();
Get.back(); Get.back();

28
lib/modules/meeting/controllers/meeting_home_controller.dart

@ -110,9 +110,9 @@ class MeetingHomeController extends GetxController {
PermissionUtil.instance PermissionUtil.instance
.requestPermission( .requestPermission(
permissionType: Permission.storage, permissionType: Permission.storage,
permissionName: "存储", permissionName: "meetingStoragePermission".tr, // 对应中文:存储
explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。", explanationText: "meetingStoragePermissionExplanation".tr, // 对应中文:我们需要访问存储权限,以便您可以选择并上传音频文件。
permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。", permanentDenialText: "meetingStoragePermissionPermanentDenial".tr, // 对应中文:检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。
) )
.then((granted) { .then((granted) {
if (granted) { if (granted) {
@ -164,17 +164,17 @@ class MeetingHomeController extends GetxController {
try { try {
await Share.shareXFiles( await Share.shareXFiles(
[XFile(filePath)], [XFile(filePath)],
text: '分享会议音频', text: 'shareAudio'.tr, // 对应中文:分享会议音频
); );
EasyLoading.showToast('分享完成!'); EasyLoading.showToast('shareComplete'.tr); // 对应中文:分享完成!
} catch (e) { } catch (e) {
EasyLoading.showToast('分享失败: ${e.toString()}'); EasyLoading.showToast('shareFailed'.trParams({'error': e.toString()})); // 对应中文:分享失败: {error}
} }
} }
void addMeetingAudio() async { void addMeetingAudio() async {
EasyLoading.show(status: '正在导入本地录音...'); EasyLoading.show(status: 'importingLocalRecordings'.tr); // 对应中文:正在导入本地录音...
try { try {
int addUp = 0; int addUp = 0;
final appDir = await appDocDir; final appDir = await appDocDir;
@ -258,19 +258,19 @@ class MeetingHomeController extends GetxController {
addUp++; addUp++;
} }
} catch (e) { } catch (e) {
print("文件处理失败: ${fileEntity.path}, 错误: $e"); print('fileProcessingFailed'.trParams({'filePath': fileEntity.path, 'error': e.toString()})); // 对应中文:文件处理失败: {filePath}, 错误: {error}
} }
} }
if (addUp > 0) EasyLoading.showSuccess('成功导入本地 $addUp 条录音'); if (addUp > 0) EasyLoading.showSuccess('importSuccess'.trParams({'count': '$addUp'})); // 对应中文:成功导入本地 {count} 条录音
} catch (e) { } catch (e) {
EasyLoading.showError('导入失败: $e'); EasyLoading.showError('importError'.trParams({'error': e.toString()})); // 对应中文:导入失败: {error}
} finally { } finally {
EasyLoading.dismiss(); EasyLoading.dismiss();
} }
} }
void addTranslatAudio() async { void addTranslatAudio() async {
EasyLoading.show(status: '正在批量导入...'); EasyLoading.show(status: 'batchImporting'.tr); // 对应中文:正在批量导入...
for (final filePath in selected) { for (final filePath in selected) {
try { try {
await _audioPlayer.setAudioSource(AudioSource.file(filePath)); await _audioPlayer.setAudioSource(AudioSource.file(filePath));
@ -308,14 +308,14 @@ class MeetingHomeController extends GetxController {
} }
} }
} catch (e) { } catch (e) {
Logger.error('导入文件失败: $filePath, error: $e'); Logger.error('importFailed'.trParams({'filePath': filePath, 'error': e.toString()})); // 对应中文:导入文件失败: {filePath}, error: {error}
} }
} }
EasyLoading.dismiss(); EasyLoading.dismiss();
} }
void addAudio([String? filePath]) async { void addAudio([String? filePath]) async {
EasyLoading.show(status: '正在导入...'); EasyLoading.show(status: 'importing'.tr); // 对应中文:正在导入...
String fileName = ""; String fileName = "";
bool isParamProvided = filePath != null; // 更清晰的变量名 bool isParamProvided = filePath != null; // 更清晰的变量名
if (filePath != null) { if (filePath != null) {
@ -331,7 +331,7 @@ class MeetingHomeController extends GetxController {
if (!_isAudioFile(file.name)) { if (!_isAudioFile(file.name)) {
Get.snackbar( Get.snackbar(
'error'.tr, // 错误 'error'.tr, // 错误
'请选择音频文件', 'pleaseSelectAudioFile'.tr, // 对应中文:请选择音频文件
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1), backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red, colorText: Colors.red,

12
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -154,13 +154,13 @@ class MeetingRecordController extends GetxController
// Set default filename based on audio type // Set default filename based on audio type
switch (audioType.value) { switch (audioType.value) {
case 0: case 0:
fileName.value = "现场录音"; fileName.value = "liveRecording".tr; //对应中文:现场录音
break; break;
case 1: case 1:
fileName.value = "音、视频录音"; fileName.value = "audioVideoRecording".tr; //对应中文:音视频录音
break; break;
case 2: case 2:
fileName.value = "通话录音"; fileName.value = "callRecording".tr; //对应中文:电话录音
break; break;
} }
newName.value = fileName.value; newName.value = fileName.value;
@ -460,13 +460,13 @@ class MeetingRecordController extends GetxController
void _resetToDefaultState() { void _resetToDefaultState() {
switch (audioType.value) { switch (audioType.value) {
case 0: case 0:
fileName.value = "现场录音"; fileName.value = "liveRecording".tr; //对应中文:现场录音
break; break;
case 1: case 1:
fileName.value = "音、视频录音"; fileName.value = "audioVideoRecording".tr; //对应中文:音视频录音
break; break;
case 2: case 2:
fileName.value = "通话录音"; fileName.value = "callRecording".tr; //对应中文:电话录音
break; break;
} }
newName.value = fileName.value; newName.value = fileName.value;

5
lib/modules/meeting/controllers/mind_map_controller.dart

@ -175,11 +175,12 @@ class MindMapController extends GetxController {
final File file = File('${tempDir.path}/$fileName'); final File file = File('${tempDir.path}/$fileName');
await file.writeAsBytes(bytes); await file.writeAsBytes(bytes);
final result = await SharePlus.instance.share(ShareParams( final result = await SharePlus.instance.share(ShareParams(
text: '分享思维导图', text: 'shareMindMap'.tr, //对应中文:分享思维导图
files: [XFile(file.path)], files: [XFile(file.path)],
)); ));
if (result.status == ShareResultStatus.success) { if (result.status == ShareResultStatus.success) {
EasyLoading.showToast('分享完成!'); EasyLoading.showToast('shareSuccess'.tr); //对应中文:分享成功
} }
} }
} }

12
lib/modules/meeting/views/bottomSheet/ai_bottom_sheet.dart

@ -30,9 +30,9 @@ class _AIBottomSheetState extends State<AIBottomSheet> {
List _dataList = []; List _dataList = [];
final List _cruxList = [ final List _cruxList = [
'代办事项', 'todoList'.tr, //对应中文:代办事项
'提取结论', 'extractConclusion'.tr, //对应中文:提取结论
'生成关键指标', 'generateKeyIndicators'.tr, //对应中文:生成关键指标
]; ];
final StringBuffer _buffer = StringBuffer(); final StringBuffer _buffer = StringBuffer();
bool _isAdd = true; bool _isAdd = true;
@ -179,7 +179,7 @@ class _AIBottomSheetState extends State<AIBottomSheet> {
Padding( Padding(
padding: EdgeInsets.only(bottom: 5.w), padding: EdgeInsets.only(bottom: 5.w),
child: Text( child: Text(
'继续提问', 'continueAsk'.tr, //对应中文:继续提问
style: TextStyle( style: TextStyle(
color: Colors.grey, color: Colors.grey,
fontSize: 13.sp, fontSize: 13.sp,
@ -226,7 +226,7 @@ class _AIBottomSheetState extends State<AIBottomSheet> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'今天我能帮您做点什么呢?', 'askAIPrompt'.tr, //对应中文:今天我能帮您做点什么呢?
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -310,7 +310,7 @@ class _AIBottomSheetState extends State<AIBottomSheet> {
height: 1.4, height: 1.4,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: '问 AI ...', hintText: 'askAI'.tr, //对应中文:问 AI ...
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.grey[400], color: Colors.grey[400],

10
lib/modules/meeting/views/bottomSheet/filtrate_drawer.dart

@ -47,7 +47,7 @@ class _FiltrateDrawerState extends State<FiltrateDrawer> {
fontSize: 14.sp, fontSize: 14.sp,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: '在所有文件中搜索', hintText: 'searchInAllFiles'.tr, // 对应中文:在所有文件中搜索
hintStyle: TextStyle( hintStyle: TextStyle(
color: Colors.black54, color: Colors.black54,
fontSize: 14.sp, fontSize: 14.sp,
@ -109,7 +109,7 @@ class _FiltrateDrawerState extends State<FiltrateDrawer> {
), ),
6.horizontalSpace, 6.horizontalSpace,
Text( Text(
'全部文件(${_homeController.originalDataList.length})', '${'allFiles'.tr}(${_homeController.originalDataList.length})', // 对应中文:全部文件
style: TextStyle( style: TextStyle(
color: Colors.black87, color: Colors.black87,
fontSize: 14.sp, fontSize: 14.sp,
@ -129,7 +129,7 @@ class _FiltrateDrawerState extends State<FiltrateDrawer> {
Padding( Padding(
padding: EdgeInsets.only(bottom: 10.w), padding: EdgeInsets.only(bottom: 10.w),
child: Text( child: Text(
'来自', 'from'.tr, // 对应中文:来自
style: TextStyle( style: TextStyle(
color: Colors.grey, color: Colors.grey,
fontSize: 14.sp, fontSize: 14.sp,
@ -174,7 +174,7 @@ class _FiltrateDrawerState extends State<FiltrateDrawer> {
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 10.w), padding: EdgeInsets.symmetric(vertical: 10.w),
child: Text( child: Text(
'排序方式', 'sortBy'.tr, // 对应中文:排序方式
style: TextStyle( style: TextStyle(
color: Colors.grey, color: Colors.grey,
fontSize: 14.sp, fontSize: 14.sp,
@ -197,7 +197,7 @@ class _FiltrateDrawerState extends State<FiltrateDrawer> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'创建时间', 'creationTime'.tr, // 对应中文:创建时间
style: TextStyle( style: TextStyle(
color: Colors.black87, color: Colors.black87,
fontSize: 14.sp, fontSize: 14.sp,

14
lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart

@ -24,7 +24,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
bool _isSpeaker = true; bool _isSpeaker = true;
String _languageText = '中文普通话(简体)'; String _languageText = 'chineseSimplified'.tr; // 对应中文:中文普通话(简体)
String _language = 'zh-CN'; String _language = 'zh-CN';
final Map _icons = { final Map _icons = {
@ -149,7 +149,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
'选择总结模版', 'selectSummaryTemplate'.tr, // 对应中文:选择总结模版
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.black87, color: Colors.black87,
@ -186,7 +186,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
child: Row( child: Row(
children: [ children: [
Text( Text(
'查看全部', 'viewAll'.tr, // 对应中文:查看全部
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: Colors.grey, color: Colors.grey,
@ -226,7 +226,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
child: Column( child: Column(
children: [ children: [
_row( _row(
'区分说话人', 'speakerIdentification'.tr, // 对应中文:区分说话人
widget: Transform.scale( widget: Transform.scale(
scale: 0.8, scale: 0.8,
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
@ -241,7 +241,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
), ),
), ),
_row( _row(
'录音语言', 'recordingLanguage'.tr, // 对应中文:录音语言
value: _languageText, value: _languageText,
isBorder: false, isBorder: false,
onTap: () { onTap: () {
@ -296,7 +296,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'立即生成', 'meetingGenerateNow'.tr, // 对应中文:立即生成
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.white, color: Colors.white,
@ -416,7 +416,7 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
), ),
), ),
child: Text( child: Text(
'上次使用', 'lastUsed'.tr, // 对应中文:上次使用
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12.sp, fontSize: 12.sp,

19
lib/modules/meeting/views/bottomSheet/member_bottom_sheet.dart

@ -63,11 +63,16 @@ class MemberBottomSheet extends StatelessWidget {
), ),
), ),
), ),
_item(Icons.access_time, Colors.red, '每月300分钟转写时长'), _item(Icons.access_time, Colors.red,
_item(Icons.description, Colors.purpleAccent, '为行业细分场景设计的专业总结模版'), 'monthlyTranscriptionDuration'.tr), //对应中文:每月300分钟转写时长
_item(Icons.auto_awesome, Colors.purpleAccent, 'Autopilot 模版自动总结'), _item(Icons.description, Colors.purpleAccent,
_item(Icons.people, Colors.red, '区分不同说话人'), 'industrySpecificSummaryTemplate'.tr), //对应中文:为行业细分场景设计的专业总结模版
_item(Icons.cloud_upload, Colors.red, '从APP、WEB导入音频进行转写和总结'), _item(Icons.auto_awesome, Colors.purpleAccent,
'autopilotTemplateSummary'.tr), //对应中文:Autopilot 模版自动总结
_item(Icons.people, Colors.red,
'distinguishDifferentSpeakers'.tr), //对应中文:区分不同说话人
_item(Icons.cloud_upload, Colors.red,
'importAudioFromAppWeb'.tr), //对应中文:从APP、WEB导入音频进行转写和总结
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 20.w), padding: EdgeInsets.symmetric(vertical: 20.w),
child: Row( child: Row(
@ -92,7 +97,7 @@ class MemberBottomSheet extends StatelessWidget {
), ),
), ),
child: Text( child: Text(
'发现更多', 'findMore'.tr, //对应中文:发现更多
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.black, color: Colors.black,
@ -113,7 +118,7 @@ class MemberBottomSheet extends StatelessWidget {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'立即绑定', 'bindImmediately'.tr, //对应中文:立即绑定
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.white, color: Colors.white,

7
lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart

@ -25,7 +25,7 @@ class _MoreBottomSheetState extends State<MoreBottomSheet> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox(height: 10.w), SizedBox(height: 10.w),
_shareItem(Icons.refresh, '重新总结', _shareItem(Icons.refresh, 'reSummarize'.tr, //重新总结
onTap: _detailsController.meetingDetails.value.tasktype >= 3 onTap: _detailsController.meetingDetails.value.tasktype >= 3
? () { ? () {
Get.back(); Get.back();
@ -43,7 +43,7 @@ class _MoreBottomSheetState extends State<MoreBottomSheet> {
); );
} }
: null), : null),
_shareItem(Icons.delete_outline, '删除', onTap: () async { _shareItem(Icons.delete_outline, 'delete'.tr, onTap: () async {
_controller.deleteMeeting( _controller.deleteMeeting(
_detailsController.meetingData.value.id, _detailsController.meetingData.value.id,
_detailsController.meetingData.value.filepath, _detailsController.meetingData.value.filepath,
@ -85,7 +85,8 @@ class _MoreBottomSheetState extends State<MoreBottomSheet> {
title, title,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.black87.withValues(alpha: onTap != null ? 1 : 0.5), color:
Colors.black87.withValues(alpha: onTap != null ? 1 : 0.5),
), ),
), ),
), ),

13
lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get_navigation/src/extension_navigation.dart'; import 'package:get/get.dart';
import 'package:get/state_manager.dart';
import '../../../../routes/app_routes.dart'; import '../../../../routes/app_routes.dart';
import '../../controllers/meeting_home_controller.dart'; import '../../controllers/meeting_home_controller.dart';
@ -18,14 +17,14 @@ class NavigationBarBottomSheet extends GetView<MeetingHomeController> {
if (controller.meetingController.type == 0) if (controller.meetingController.type == 0)
_buildItem( _buildItem(
context, // 传递 context 参数 context, // 传递 context 参数
'开始录音', 'startRecording'.tr, // 对应中文:开始录音
Icons.mic_rounded, Icons.mic_rounded,
Colors.red, Colors.red,
), ),
if (controller.meetingController.type == 1) if (controller.meetingController.type == 1)
_buildItem( _buildItem(
context, // 传递 context 参数 context, // 传递 context 参数
'现场录音', 'liveRecording'.tr, // 对应中文:现场录音
Icons.mic_rounded, Icons.mic_rounded,
Colors.red, Colors.red,
onTap: () async { onTap: () async {
@ -39,7 +38,7 @@ class NavigationBarBottomSheet extends GetView<MeetingHomeController> {
if (controller.meetingController.type == 1) if (controller.meetingController.type == 1)
_buildItem( _buildItem(
context, // 传递 context 参数 context, // 传递 context 参数
'音、视频录音', 'audioVideoRecording'.tr, // 对应中文:音、视频录音
Icons.videocam, Icons.videocam,
Colors.green, Colors.green,
onTap: controller.bleManager.isConnected onTap: controller.bleManager.isConnected
@ -55,7 +54,7 @@ class NavigationBarBottomSheet extends GetView<MeetingHomeController> {
if (controller.meetingController.type == 1) if (controller.meetingController.type == 1)
_buildItem( _buildItem(
context, // 传递 context 参数 context, // 传递 context 参数
'通话录音', 'callRecording'.tr, // 对应中文:通话录音
Icons.phone, Icons.phone,
Colors.orange, Colors.orange,
onTap: controller.bleManager.isConnected onTap: controller.bleManager.isConnected
@ -70,7 +69,7 @@ class NavigationBarBottomSheet extends GetView<MeetingHomeController> {
), ),
_buildItem( _buildItem(
context, // 传递 context 参数 context, // 传递 context 参数
'导入音频', 'importAudio'.tr, // 对应中文:导入音频
Icons.file_upload_outlined, Icons.file_upload_outlined,
Colors.deepPurple, Colors.deepPurple,
onTap: () { onTap: () {

24
lib/modules/meeting/views/bottomSheet/record_language_bottom_sheet.dart

@ -14,17 +14,17 @@ class RecordLanguageBottomSheet extends StatefulWidget {
class _RecordLanguageBottomSheetState extends State<RecordLanguageBottomSheet> { class _RecordLanguageBottomSheetState extends State<RecordLanguageBottomSheet> {
final List _languages = [ final List _languages = [
{'name': '中文普通话(简体)', 'value': 'zh-CN'}, {'name': 'chinese'.tr, 'value': 'zh-CN'}, // 对应中文:中文普通话(简体)
{'name': '粤语', 'value': 'cant'}, {'name': 'cantonese'.tr, 'value': 'cant'}, // 对应中文:粤语
{'name': '四川话', 'value': 'sc'}, {'name': 'sichuan'.tr, 'value': 'sc'}, // 对应中文:四川话
{'name': '上海话', 'value': 'zh_shanghai'}, {'name': 'shanghai'.tr, 'value': 'zh_shanghai'}, // 对应中文:上海话
{'name': '英文', 'value': 'en-US'}, {'name': 'english'.tr, 'value': 'en-US'}, // 对应中文:英文
{'name': '日语', 'value': 'ja-JP'}, {'name': 'japanese'.tr, 'value': 'ja-JP'}, // 对应中文:日语
{'name': '韩语', 'value': 'ko-KR'}, {'name': 'korean'.tr, 'value': 'ko-KR'}, // 对应中文:韩语
{'name': '法语', 'value': 'fr-FR'}, {'name': 'french'.tr, 'value': 'fr-FR'}, // 对应中文:法语
{'name': '西班牙语', 'value': 'es-MX'}, {'name': 'spanish'.tr, 'value': 'es-MX'}, // 对应中文:西班牙语
{'name': '葡萄牙语', 'value': 'pt-BR'}, {'name': 'portuguese'.tr, 'value': 'pt-BR'}, // 对应中文:葡萄牙语
{'name': '印尼语', 'value': 'id-ID'}, {'name': 'indonesian'.tr, 'value': 'id-ID'}, // 对应中文:印尼语
]; ];
@override @override
@ -40,7 +40,7 @@ class _RecordLanguageBottomSheetState extends State<RecordLanguageBottomSheet> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'录音语言', 'recordLanguage'.tr, // 对应中文:录音语言
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.black87, color: Colors.black87,

2
lib/modules/meeting/views/bottomSheet/second_level_bottom_sheet.dart

@ -148,7 +148,7 @@ class _SecondLevelBottomSheetState extends State<SecondLevelBottomSheet> {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'导出', 'export'.tr, // 对应中文:导出
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.white, color: Colors.white,

41
lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart

@ -79,7 +79,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
_box([ _box([
_shareItem( _shareItem(
Icons.link, Icons.link,
'分享链接', 'shareLink'.tr, // 对应中文:分享链接
isBorder: false, isBorder: false,
onTap: () async { onTap: () async {
Get.back(); Get.back();
@ -110,20 +110,20 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
previewThumbnail: thumbnail, previewThumbnail: thumbnail,
)); ));
if (result.status == ShareResultStatus.success) { if (result.status == ShareResultStatus.success) {
EasyLoading.showToast('分享完成!'); EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成
} }
}, },
), ),
]), ]),
_box([ _box([
_shareItem(Icons.copy, '复制转写', _shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写
onTap: _controller.meetingDetails.value.tasktype >= 2 onTap: _controller.meetingDetails.value.tasktype >= 2
? () { ? () {
Get.back(); Get.back();
_copy(_getAllText()); _copy(_getAllText());
} }
: null), : null),
_shareItem(Icons.copy, '复制总结', _shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结
isBorder: false, isBorder: false,
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 3
? () { ? () {
@ -133,11 +133,14 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
: null), : null),
]), ]),
_box([ _box([
_shareItem(Icons.multitrack_audio, '导出音频', onTap: () { _shareItem(
Icons.multitrack_audio, 'exportAudio'.tr, // 对应中文:导出音频
onTap: () {
Get.back(); Get.back();
_share(_controller.meetingData.value.filepath, '分享会议音频'); _share(_controller.meetingData.value.filepath,
'shareMeetingAudio'.tr); // 对应中文:分享会议音频
}), }),
_shareItem(Icons.message, '导出转写', _shareItem(Icons.message, 'exportTranscript'.tr, // 对应中文:导出转写
onTap: _controller.meetingDetails.value.tasktype >= 2 onTap: _controller.meetingDetails.value.tasktype >= 2
? () async { ? () async {
Get.back(); Get.back();
@ -146,17 +149,18 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
'${tempDir.path}/${_controller.meetingData.value.title}_speaker.txt'); '${tempDir.path}/${_controller.meetingData.value.title}_speaker.txt');
String allText = _getAllText(); String allText = _getAllText();
await file.writeAsString(allText); await file.writeAsString(allText);
_share(file.path, '分享会议转写'); _share(file.path,
'exportTranscript'.tr); // 对应中文:分享会议转写
} }
: null), : null),
_shareItem(Icons.article, '导出总结', _shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 3
? () async { ? () async {
Get.back(); Get.back();
Get.bottomSheet( Get.bottomSheet(
SecondLevelBottomSheet( SecondLevelBottomSheet(
'导出总结', 'exportSummary'.tr, // 对应中文:导出总结
'导出为', 'exportAs'.tr, // 对应中文:导出为
(int index) async { (int index) async {
if (index == 0) { if (index == 0) {
// 导出TXT // 导出TXT
@ -166,7 +170,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
'${tempDir.path}/${_controller.meetingData.value.title}_summary.txt'); '${tempDir.path}/${_controller.meetingData.value.title}_summary.txt');
await file.writeAsString(_controller await file.writeAsString(_controller
.meetingDetails.value.summary); .meetingDetails.value.summary);
_share(file.path, '分享会议总结'); _share(file.path,
'exportSummary'.tr); // 对应中文:分享会议总结
} else { } else {
// 导出PDF // 导出PDF
_exportMarkdownPdf(); _exportMarkdownPdf();
@ -197,7 +202,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
: null), : null),
_shareItem( _shareItem(
Icons.lan, Icons.lan,
'导出思维导图', 'shareMindMap'.tr, // 对应中文:分享思维导图
isBorder: false, isBorder: false,
onTap: _controller.meetingDetails.value.tasktype >= 3 onTap: _controller.meetingDetails.value.tasktype >= 3
? () { ? () {
@ -298,7 +303,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
files: [XFile(filepath)], files: [XFile(filepath)],
)); ));
if (result.status == ShareResultStatus.success) { if (result.status == ShareResultStatus.success) {
EasyLoading.showToast('分享完成!'); EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成!
} }
} }
@ -307,8 +312,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
text: text, text: text,
)).then( )).then(
(value) => Get.snackbar( (value) => Get.snackbar(
'success'.tr, // 成功 'success'.tr, // 对应中文:成功
'复制成功', 'copySuccess'.tr, // 对应中文:复制成功
backgroundColor: Colors.blue.withValues(alpha: 0.8), backgroundColor: Colors.blue.withValues(alpha: 0.8),
colorText: Colors.white, colorText: Colors.white,
), ),
@ -340,7 +345,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
} }
Future<void> _exportMarkdownPdf() async { Future<void> _exportMarkdownPdf() async {
EasyLoading.show(status: '正在导出PDF...'); EasyLoading.show(status: 'exportingPDF'.tr); // 对应中文:正在导出PDF...
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
final file = File( final file = File(
'${tempDir.path}/${_controller.meetingData.value.title}_summary.pdf'); '${tempDir.path}/${_controller.meetingData.value.title}_summary.pdf');
@ -357,7 +362,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
build: (context) => markdownWidgets, build: (context) => markdownWidgets,
)); ));
await file.writeAsBytes(await markdownPdf.save()); await file.writeAsBytes(await markdownPdf.save());
await _share(file.path, '分享会议总结'); await _share(file.path, 'exportSummary'.tr); // 对应中文:分享会议总结
EasyLoading.dismiss(); EasyLoading.dismiss();
} }
} }

13
lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart

@ -100,7 +100,7 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'命名发言者', 'nameSpeaker'.tr, // 对应中文:命名发言者
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.black87, color: Colors.black87,
@ -209,7 +209,7 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
Padding( Padding(
padding: EdgeInsets.only(top: 10.w, bottom: 5.w), padding: EdgeInsets.only(top: 10.w, bottom: 5.w),
child: Text( child: Text(
'最近使用过的名称', 'recentlyUsedNames'.tr, // 对应中文:最近使用过的名称
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: Colors.grey[600]!, color: Colors.grey[600]!,
@ -259,7 +259,7 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
child: Row( child: Row(
children: [ children: [
Text( Text(
'总计', 'totalTime'.tr, // 对应中文:总计
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: Colors.grey, color: Colors.grey,
@ -431,7 +431,7 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 5.w), padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text( child: Text(
'总结的发言者将自动更新', 'summarySpeakerWillBeUpdatedAutomatically'.tr, // 对应中文:总结的发言者将自动更新
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey[600], color: Colors.grey[600],
@ -453,7 +453,7 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'取消', 'cancel'.tr, // 对应中文:取消
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.black, color: Colors.black,
@ -474,7 +474,8 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'确认', 'confirm'.tr, // 对应中文:确认
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.white, color: Colors.white,

15
lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart

@ -71,7 +71,7 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'命名当前发言者', 'nameCurrentSpeaker'.tr, // 对应中文:命名当前发言者
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.black87, color: Colors.black87,
@ -158,7 +158,7 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
children: [ children: [
if (index == 0) if (index == 0)
Text( Text(
'最近使用过的名称', 'recentlyUsedNames'.tr, // 对应中文:最近使用过的名称
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: Colors.grey[600]!, color: Colors.grey[600]!,
@ -189,15 +189,18 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
}, },
), ),
), ),
_row('应用到当前段落', _type == 0, onTap: () { _row(
'applyToCurrentParagraph'.tr, // 对应中文:应用到当前段落
_type == 0, onTap: () {
setState(() { setState(() {
_type = 0; _type = 0;
}); });
}), }),
_row( _row(
'应用到这个发言者的所有段落', 'applyToAllParagraphs'.tr, // 对应中文:应用到这个发言者的所有段落
_type == 1, _type == 1,
description: '总结将根据模版自动更新', description:
'summaryWillBeUpdatedAutomatically'.tr, // 对应中文:总结将根据模版自动更新
onTap: () { onTap: () {
setState(() { setState(() {
_type = 1; _type = 1;
@ -216,7 +219,7 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'保存', 'save'.tr, // 对应中文:保存
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.white, color: Colors.white,

4
lib/modules/meeting/views/meeting_connect_view.dart

@ -32,7 +32,7 @@ class MeetingConnectView extends GetView<MeetingConnectController> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
title: Text( title: Text(
'蓝牙连接', 'bluetoothConnection'.tr, // 对应中文:蓝牙连接
style: TextStyle( style: TextStyle(
fontSize: 17.sp, fontSize: 17.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -63,7 +63,7 @@ class MeetingConnectView extends GetView<MeetingConnectController> {
), ),
Obx( Obx(
() => controller.isRescan.value () => controller.isRescan.value
? _button('重新连接', Icons.refresh, onTap: () { ? _button('meetingReconnect'.tr, Icons.refresh, onTap: () { // 对应中文:重新连接
controller.startScan(); controller.startScan();
}) })
: const SizedBox(), : const SizedBox(),

20
lib/modules/meeting/views/meeting_details_view.dart

@ -301,10 +301,10 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
child: Obx( child: Obx(
() => Row( () => Row(
children: [ children: [
_iconItem(Icons.summarize, '概览', 0), _iconItem(Icons.summarize, 'overview'.tr, 0), // 对应中文:概览
_iconItem(Icons.message, '转写', 1), _iconItem(Icons.message, 'transcription'.tr, 1), // 对应中文:转写
_iconItem(Icons.article, '总结', 2), _iconItem(Icons.article, 'summary'.tr, 2), // 对应中文:总结
_iconItem(Icons.lan, '思维导图', 3), _iconItem(Icons.lan, 'mindMap'.tr, 3), // 对应中文:思维导图
], ],
), ),
), ),
@ -327,12 +327,12 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
? const SizedBox() ? const SizedBox()
: controller.meetingDetails.value.tasktype == 1 || : controller.meetingDetails.value.tasktype == 1 ||
controller.meetingDetails.value.tasktype == 2 controller.meetingDetails.value.tasktype == 2
? _meetingWait('正在分析...') ? _meetingWait('analyzing'.tr) // 对应中文:正在分析...
: const OverviewTab(), : const OverviewTab(),
), ),
Obx( Obx(
() => controller.meetingDetails.value.tasktype == 1 () => controller.meetingDetails.value.tasktype == 1
? _meetingWait('正在转写...') ? _meetingWait('transcribing'.tr) // 对应中文:正在转写...
: const SpeechTab(), : const SpeechTab(),
), ),
Obx( Obx(
@ -340,8 +340,8 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
controller.meetingDetails.value.tasktype == 2 controller.meetingDetails.value.tasktype == 2
? _meetingWait( ? _meetingWait(
controller.meetingDetails.value.tasktype == 1 controller.meetingDetails.value.tasktype == 1
? '正在转写...' ? 'transcribing'.tr // 对应中文:正在转写...
: '正在总结...') : 'summarizing'.tr) // 对应中文:正在总结...
: const SummaryTab(), : const SummaryTab(),
), ),
Obx( Obx(
@ -351,8 +351,8 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
? const SizedBox() ? const SizedBox()
: _meetingWait( : _meetingWait(
controller.meetingDetails.value.tasktype == 1 controller.meetingDetails.value.tasktype == 1
? '正在转写...' ? 'transcribing'.tr // 对应中文:正在转写...
: '正在总结...'), : 'summarizing'.tr), // 对应中文:正在总结...
), ),
], ],
), ),

10
lib/modules/meeting/views/meeting_home_view.dart

@ -86,7 +86,7 @@ class _MeetingHomeViewState extends State<MeetingHomeView>
5.horizontalSpace, 5.horizontalSpace,
Obx( Obx(
() => Text( () => Text(
'${_controller.filtrateType.value.isNotEmpty ? _controller.filtrateType : '全部文件'}(${_controller.dataList.length})', '${_controller.filtrateType.value.isNotEmpty ? _controller.filtrateType : 'allFiles'.tr}(${_controller.dataList.length})', // 对应中文:全部文件
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp, fontSize: 16.sp,
@ -136,7 +136,7 @@ class _MeetingHomeViewState extends State<MeetingHomeView>
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w), padding: EdgeInsets.symmetric(horizontal: 5.w),
child: Text( child: Text(
'连接', 'connect'.tr, // 对应中文:连接
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 14.sp, fontSize: 14.sp,
@ -329,10 +329,10 @@ class _MeetingHomeViewState extends State<MeetingHomeView>
padding: EdgeInsets.only(top: 5.w), padding: EdgeInsets.only(top: 5.w),
child: Text( child: Text(
taskData['tasktype'] == 1 taskData['tasktype'] == 1
? '正在转写...' ? 'transcribing'.tr // 对应中文:正在转写...
: taskData['tasktype'] == 2 : taskData['tasktype'] == 2
? '正在总结...' ? 'summarizing'.tr // 对应中文:正在总结...
: '总结完成', : 'summaryComplete'.tr, // 对应中文:总结完成
style: TextStyle( style: TextStyle(
color: Colors.blue, color: Colors.blue,
fontSize: 10.sp, fontSize: 10.sp,

34
lib/modules/meeting/views/meeting_mine_view.dart

@ -54,7 +54,7 @@ class _MeetingMineViewState extends State<MeetingMineView>
_box([ _box([
_item( _item(
Icons.article_outlined, Icons.article_outlined,
'转写记录', 'transcriptionRecords'.tr, // 对应中文:转写记录
onTap: () { onTap: () {
Get.to(() => const MeetingTranscriptionView()); Get.to(() => const MeetingTranscriptionView());
}, },
@ -63,14 +63,14 @@ class _MeetingMineViewState extends State<MeetingMineView>
_box([ _box([
_item( _item(
Icons.layers_outlined, Icons.layers_outlined,
'模版社区', 'templateCommunity'.tr, // 对应中文:模版社区
description: '尽请期待', description: 'comingSoon'.tr, // 对应中文:尽请期待
onTap: () {}, onTap: () {},
), ),
_item( _item(
Icons.sync_alt_outlined, Icons.sync_alt_outlined,
'集成', 'integration'.tr, // 对应中文:集成
description: '尽请期待', description: 'comingSoon'.tr, // 对应中文:尽请期待
onTap: () {}, onTap: () {},
), ),
]), ]),
@ -80,8 +80,8 @@ class _MeetingMineViewState extends State<MeetingMineView>
Icons.cloud_upload_outlined, Icons.cloud_upload_outlined,
'VOITRANS PRIVATE CLOUD', 'VOITRANS PRIVATE CLOUD',
description: _meetingController.user['privatecloud'] == 1 description: _meetingController.user['privatecloud'] == 1
? '开启' ? 'enabled'.tr // 对应中文:开启
: '关闭', : 'disabled'.tr, // 对应中文:关闭
onTap: () { onTap: () {
Get.to(() => const MeetingCloudView()); Get.to(() => const MeetingCloudView());
}, },
@ -97,7 +97,7 @@ class _MeetingMineViewState extends State<MeetingMineView>
), ),
_item( _item(
Icons.settings_remote_outlined, Icons.settings_remote_outlined,
'我的VOITRANS设备', 'myVoitransDevice'.tr, // 对应中文:我的VOITRANS设备
onTap: () { onTap: () {
Get.toNamed(Routes.devices); Get.toNamed(Routes.devices);
}, },
@ -106,14 +106,14 @@ class _MeetingMineViewState extends State<MeetingMineView>
_box([ _box([
_item( _item(
Icons.help_outline_rounded, Icons.help_outline_rounded,
'帮助与反馈', 'helpAndFeedback'.tr, // 对应中文:帮助与反馈
onTap: () { onTap: () {
Get.toNamed(Routes.feedback); Get.toNamed(Routes.feedback);
}, },
), ),
_item( _item(
Icons.info_outline_rounded, Icons.info_outline_rounded,
'关于VOITRANS', 'aboutVoitrans'.tr, // 对应中文:关于VOITRANS
description: 'v1.0.0', description: 'v1.0.0',
onTap: () {}, onTap: () {},
), ),
@ -189,17 +189,17 @@ class _MeetingMineViewState extends State<MeetingMineView>
children: [ children: [
_statisticsItem( _statisticsItem(
'${_controller.days.value}', '${_controller.days.value}',
'天', 'meetingDays'.tr, // 对应中文:天
Colors.lime, Colors.lime,
), ),
_statisticsItem( _statisticsItem(
'${_controller.numberFiles.value}', '${_controller.numberFiles.value}',
'文件', 'files'.tr, // 对应中文:文件
Colors.lightGreen, Colors.lightGreen,
), ),
_statisticsItem( _statisticsItem(
'${_controller.hourage.value}', '${_controller.hourage.value}',
'小时', 'meetingHours'.tr, // 对应中文:小时
Colors.blueGrey, Colors.blueGrey,
), ),
], ],
@ -305,7 +305,7 @@ class _MeetingMineViewState extends State<MeetingMineView>
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Voitrans AI会员', 'voitransAiMember'.tr, // 对应中文:Voitrans AI会员
style: TextStyle( style: TextStyle(
color: MeetingUIUtils.getTextColor(isDarkMode), color: MeetingUIUtils.getTextColor(isDarkMode),
fontSize: 16.sp, fontSize: 16.sp,
@ -350,7 +350,7 @@ class _MeetingMineViewState extends State<MeetingMineView>
), ),
5.horizontalSpace, 5.horizontalSpace,
Text( Text(
'解锁 Starter 会员', 'unlockStarterMember'.tr, // 对应中文:解锁 Starter 会员
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18.sp, fontSize: 18.sp,
@ -365,7 +365,7 @@ class _MeetingMineViewState extends State<MeetingMineView>
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'绑定 Voitrans 设备可激活 Starter 会员', 'bindDeviceToActivate'.tr, // 对应中文:绑定 Voitrans 设备可激活 Starter 会员
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12.sp, fontSize: 12.sp,
@ -385,7 +385,7 @@ class _MeetingMineViewState extends State<MeetingMineView>
), ),
), ),
child: Text( child: Text(
'立即解锁', 'unlockNow'.tr, // 对应中文:立即解锁
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 14.sp, fontSize: 14.sp,

2
lib/modules/meeting/views/meeting_record_view.dart

@ -347,7 +347,7 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
// 标题 // 标题
Text( Text(
'editMeetingTitle'.tr, 'editMeetingTitle'.tr, // 对应中文:修改会议标题(非必填)
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 20.sp, fontSize: 20.sp,

6
lib/modules/meeting/views/meeting_view.dart

@ -35,11 +35,11 @@ class MeetingView extends GetView<MeetingController> {
right: 0, right: 0,
child: Obx( child: Obx(
() => BottomNavigationBar( () => BottomNavigationBar(
items: const [ items: [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.folder_outlined), icon: Icon(Icons.folder_outlined),
activeIcon: Icon(Icons.folder), activeIcon: Icon(Icons.folder),
label: '文件', label: 'files'.tr, // 对应中文:文件
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: SizedBox(), icon: SizedBox(),
@ -48,7 +48,7 @@ class MeetingView extends GetView<MeetingController> {
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person_outline), icon: Icon(Icons.person_outline),
activeIcon: Icon(Icons.person), activeIcon: Icon(Icons.person),
label: '我的', label: 'mine'.tr, // 对应中文:我的
), ),
], ],
backgroundColor: backgroundColor:

9
lib/modules/meeting/views/mine/meeting_cloud_view.dart

@ -60,7 +60,7 @@ class _MeetingCloudViewState extends State<MeetingCloudView> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
title: Text( title: Text(
'同步到云端', 'syncToCloud'.tr, // 对应中文:同步到云端
style: TextStyle( style: TextStyle(
fontSize: 17.sp, fontSize: 17.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -101,7 +101,7 @@ class _MeetingCloudViewState extends State<MeetingCloudView> {
Padding( Padding(
padding: EdgeInsets.only(top: 20.w, bottom: 10.w), padding: EdgeInsets.only(top: 20.w, bottom: 10.w),
child: Text( child: Text(
'同步到云端', 'syncToCloud'.tr, // 对应中文:同步到云端
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 18.sp, fontSize: 18.sp,
@ -110,7 +110,8 @@ class _MeetingCloudViewState extends State<MeetingCloudView> {
), ),
), ),
Text( Text(
'自动将你的录音同步到云端,你可以通过任意VOITRANS APP或VOITRANS Web(web.voitrans.com)播放、管理和分享录音。', 'syncToCloudTip'
.tr, // 对应中文:自动将你的录音同步到云端,你可以通过任意VOITRANS APP或VOITRANS Web(web.voitrans.com)播放、管理和分享录音。
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
@ -132,7 +133,7 @@ class _MeetingCloudViewState extends State<MeetingCloudView> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'同步VOITRANS APP', 'syncToVoitransApp'.tr, // 对应中文:同步VOITRANS APP
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp, fontSize: 16.sp,

86
lib/modules/meeting/views/mine/meeting_member_view.dart

@ -43,7 +43,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
title: Text( title: Text(
'VOITRANS AI会员', 'voitransAiMember'.tr, // 对应中文:VOITRANS AI会员
style: TextStyle( style: TextStyle(
fontSize: 17.sp, fontSize: 17.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -133,7 +133,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
), ),
5.horizontalSpace, 5.horizontalSpace,
Text( Text(
'解锁 Starter 会员', 'unlockStarterMember'.tr, // 对应中文:解锁 Starter 会员
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18.sp, fontSize: 18.sp,
@ -148,7 +148,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'绑定 Voitrans 设备可激活 Starter 会员', 'bindDeviceToActivate'.tr, // 对应中文:绑定 Voitrans 设备可激活 Starter 会员
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12.sp, fontSize: 12.sp,
@ -168,7 +168,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
), ),
), ),
child: Text( child: Text(
'立即解锁', 'unlockNow'.tr, // 对应中文:立即解锁
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 14.sp, fontSize: 14.sp,
@ -213,7 +213,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
) )
: null, : null,
child: Text( child: Text(
'对比', 'comparison'.tr, // 对应中文:对比
style: TextStyle( style: TextStyle(
color: _tabsIndex == 0 ? Colors.black : Colors.white, color: _tabsIndex == 0 ? Colors.black : Colors.white,
fontSize: 14.sp, fontSize: 14.sp,
@ -241,7 +241,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
) )
: null, : null,
child: Text( child: Text(
'详细信息', 'detailedInfo'.tr, // 对应中文:详细信息
style: TextStyle( style: TextStyle(
color: _tabsIndex == 1 ? Colors.black : Colors.white, color: _tabsIndex == 1 ? Colors.black : Colors.white,
fontSize: 14.sp, fontSize: 14.sp,
@ -342,24 +342,24 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
), ),
), ),
_tableItem( _tableItem(
'每月转写时长', 'monthlyTranscriptionTime'.tr, // 对应中文:每月转写时长
starter: '300', starter: '300',
pro: '1200', pro: '1200',
unlimited: '无限', unlimited: 'unlimited'.tr, // 对应中文:无限
isGradient: true, isGradient: true,
), ),
_tableItem( _tableItem(
'专业模版', 'professionalTemplates'.tr, // 对应中文:专业模版
starter: '有限', starter: 'limited'.tr, // 对应中文:有限
pro: '全量模版', pro: 'fullTemplates'.tr, // 对应中文:全量模版
unlimited: '全量模版', unlimited: 'fullTemplates'.tr, // 对应中文:全量模版
), ),
_tableItem( _tableItem(
'专业术语', 'professionalTerms'.tr, // 对应中文:专业术语
starter: '—', starter: '—',
), ),
_tableItem( _tableItem(
'自定义模版', 'customTemplates'.tr, // 对应中文:自定义模版
starter: '—', starter: '—',
), ),
_tableItem( _tableItem(
@ -367,22 +367,22 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
starter: '—', starter: '—',
), ),
_tableItem( _tableItem(
'可视化思维导图', 'visualMindMap'.tr, // 对应中文:可视化思维导图
), ),
_tableItem( _tableItem(
'区分说话人', 'speakerIdentification'.tr, // 对应中文:区分说话人
), ),
_tableItem( _tableItem(
'Private Cloud Sync', 'Private Cloud Sync',
), ),
_tableItem( _tableItem(
'AI 人声增强', 'aiVoiceEnhancement'.tr, // 对应中文:AI 人声增强
), ),
_tableItem( _tableItem(
'音频导入', 'audioImport'.tr, // 对应中文:音频导入
), ),
_tableItem( _tableItem(
'支持多种导出格式', 'multipleExportFormats'.tr, // 对应中文:支持多种导出格式
), ),
], ],
); );
@ -394,62 +394,62 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
_detailedItem( _detailedItem(
Icons.description, Icons.description,
Colors.purpleAccent, Colors.purpleAccent,
'专业总结模版', 'professionalSummaryTemplates'.tr, // 对应中文:专业总结模版
'专业模版设计,提高您的工作效率。根据行业需求量身定制。', 'professionalTemplateDesc'.tr, // 对应中文:专业模版设计,提高您的工作效率。根据行业需求量身定制。
), ),
_detailedItem( _detailedItem(
Icons.edit_note, Icons.edit_note,
Colors.purpleAccent, Colors.purpleAccent,
'自定义模版', 'customTemplates'.tr, // 对应中文:自定义模版
'您可以根据需求自定义总结 Prompts,并用它们生成所需的总结。', 'customTemplateDesc'.tr, // 对应中文:您可以根据需求自定义总结 Prompts,并用它们生成所需的总结。
), ),
_detailedItem( _detailedItem(
Icons.psychology, Icons.psychology,
Colors.purpleAccent, Colors.purpleAccent,
'Ask AI', 'Ask AI',
'从录音中智能挖掘更深入信息,一键生成数据表格、关键讨论结论、To Do、汇报邮件等格式内容。', 'askAiDesc'.tr, // 对应中文:从录音中智能挖掘更深入信息,一键生成数据表格、关键讨论结论、To Do、汇报邮件等格式内容。
), ),
_detailedItem( _detailedItem(
Icons.account_tree, Icons.account_tree,
Colors.purpleAccent, Colors.purpleAccent,
'可视化思维导图', 'visualMindMap'.tr, // 对应中文:可视化思维导图
'超越总结-用思维导图整理和可视化要点,更有效地理解全文。', 'visualMindMapDesc'.tr, // 对应中文:超越总结-用思维导图整理和可视化要点,更有效地理解全文。
), ),
_detailedItem( _detailedItem(
Icons.groups, Icons.groups,
Colors.purpleAccent, Colors.purpleAccent,
'模版社区', 'templateCommunity'.tr, // 对应中文:模版社区
'发现、分享,并从用户创建的模版库中获得灵感。', 'templateCommunityDesc'.tr, // 对应中文:发现、分享,并从用户创建的模版库中获得灵感。
), ),
_detailedItem( _detailedItem(
Icons.people, Icons.people,
Colors.red, Colors.red,
'区分说话人', 'speakerIdentification'.tr, // 对应中文:区分说话人
'音频转写将支持区分发言者,更直观的还原会议或电话通话内容。同时总结将更加准确。', 'speakerIdentificationDesc'.tr, // 对应中文:音频转写将支持区分发言者,更直观的还原会议或电话通话内容。同时总结将更加准确。
), ),
_detailedItem( _detailedItem(
Icons.cloud_sync, Icons.cloud_sync,
Colors.red, Colors.red,
'Private Cloud Sync', 'Private Cloud Sync',
'您的录音和转写等数据将安全存储在专属的云端服务器中,无论是上传还是下载,全程加密处理,确保隐私安全。', 'privateCloudSyncDesc'.tr, // 对应中文:您的录音和转写等数据将安全存储在专属的云端服务器中,无论是上传还是下载,全程加密处理,确保隐私安全。
), ),
_detailedItem( _detailedItem(
Icons.graphic_eq, Icons.graphic_eq,
Colors.red, Colors.red,
'AI人声增强', 'aiVoiceEnhancement'.tr, // 对应中文:AI人声增强
'Al 人声增强支持多档降噪设置,智能消除背景噪音,增强语音清晰度,提升音频回放效果。', 'aiVoiceEnhancementDesc'.tr, // 对应中文:Al 人声增强支持多档降噪设置,智能消除背景噪音,增强语音清晰度,提升音频回放效果。
), ),
_detailedItem( _detailedItem(
Icons.file_upload, Icons.file_upload,
Colors.red, Colors.red,
'音频导入', 'audioImport'.tr, // 对应中文:音频导入
'可以轻松从本地或第三方应用导入音频文件,如语音备忘录、Google云端硬盘等。', 'audioImportDesc'.tr, // 对应中文:可以轻松从本地或第三方应用导入音频文件,如语音备忘录、Google云端硬盘等。
), ),
_detailedItem( _detailedItem(
Icons.file_download, Icons.file_download,
Colors.blue, Colors.blue,
'多种导出格式', 'multipleExportFormats'.tr, // 对应中文:多种导出格式
'导出您的音频录音、转写文本和摘要为各种格式(TXT、PDF、JPEG),以满足您的需求。', 'multipleExportFormatsDesc'.tr, // 对应中文:导出您的音频录音、转写文本和摘要为各种格式(TXT、PDF、JPEG),以满足您的需求。
), ),
], ],
); );
@ -644,7 +644,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
margin: EdgeInsets.only(top: 30.w), margin: EdgeInsets.only(top: 30.w),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'升级您的会员', 'upgradeMembership'.tr, // 对应中文:升级您的会员
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 24.sp, fontSize: 24.sp,
@ -675,7 +675,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
) )
: null, : null,
child: Text( child: Text(
'年订阅', 'yearlySubscription'.tr, // 对应中文:年订阅
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16.sp, fontSize: 16.sp,
@ -701,7 +701,7 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
) )
: null, : null,
child: Text( child: Text(
'月订阅', 'monthlySubscription'.tr, // 对应中文:月订阅
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16.sp, fontSize: 16.sp,
@ -714,9 +714,9 @@ class _MeetingMemberViewState extends State<MeetingMemberView> {
), ),
if (_upgradeIndex == 0) if (_upgradeIndex == 0)
_upgradeItem( _upgradeItem(
'Pro ¥60/月', 'proMonthly'.tr, // 对应中文:Pro ¥60/月
'7天免费试用,之后按¥800/年。试用期结束前可随时取消。', 'proMonthlyDesc'.tr, // 对应中文:7天免费试用,之后按¥800/年。试用期结束前可随时取消。
'7天免费试用', 'sevenDayTrial'.tr, // 对应中文:7天免费试用
onTap: () {}, onTap: () {},
), ),
if (_upgradeIndex == 0) if (_upgradeIndex == 0)

2
lib/modules/meeting/views/mine/meeting_transcription_view.dart

@ -37,7 +37,7 @@ class MeetingTranscriptionView extends GetView<MeetingHomeController> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
title: Text( title: Text(
'转写记录', 'transcriptionRecord'.tr, // 对应中文:转写记录
style: TextStyle( style: TextStyle(
fontSize: 17.sp, fontSize: 17.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

8
lib/modules/meeting/views/tabs/overview_tab.dart

@ -71,7 +71,7 @@ class _OverviewTabState extends State<OverviewTab> {
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 10.w), padding: EdgeInsets.symmetric(vertical: 10.w),
child: Text( child: Text(
'分析出${_dataList.length}个关键事件。', 'keyEventsAnalyzed'.trParams({'count': '${_dataList.length}'}), // 对应中文:分析出{count}个关键事件。
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey, color: isDarkMode ? Colors.grey[400] : Colors.grey,
fontSize: 12.sp, fontSize: 12.sp,
@ -120,7 +120,7 @@ class _OverviewTabState extends State<OverviewTab> {
width: 150.w, width: 150.w,
child: _button( child: _button(
Icons.auto_awesome, Icons.auto_awesome,
'生成', 'generate'.tr, // 对应中文:生成
() { () {
Get.bottomSheet( Get.bottomSheet(
const GenerateBottomSheet(), const GenerateBottomSheet(),
@ -210,7 +210,7 @@ class _OverviewTabState extends State<OverviewTab> {
), ),
), ),
Text( Text(
'概览', 'overview'.tr, // 对应中文:概览
style: TextStyle( style: TextStyle(
color: MeetingUIUtils.getTextColor(isDarkMode), color: MeetingUIUtils.getTextColor(isDarkMode),
fontSize: 18.sp, fontSize: 18.sp,
@ -218,7 +218,7 @@ class _OverviewTabState extends State<OverviewTab> {
), ),
), ),
Text( Text(
'AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。', 'aiAnalysisDesc'.tr, // 对应中文:AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。
style: TextStyle( style: TextStyle(
color: MeetingUIUtils.getSecondaryTextColor(isDarkMode), color: MeetingUIUtils.getSecondaryTextColor(isDarkMode),
fontSize: 14.sp, fontSize: 14.sp,

6
lib/modules/meeting/views/tabs/speech_tab.dart

@ -256,7 +256,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
!controller.isSpeakerText.value) !controller.isSpeakerText.value)
_button( _button(
Icons.edit, Icons.edit,
'编辑', 'editSpeaker'.tr, // 对应中文:编辑
() { () {
controller.isSpeakerText.value = true; controller.isSpeakerText.value = true;
controller.dragOffset.value = 0; controller.dragOffset.value = 0;
@ -267,7 +267,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
!controller.isSpeakerText.value) !controller.isSpeakerText.value)
_button( _button(
Icons.supervisor_account, Icons.supervisor_account,
'命名发言者', 'nameSpeaker'.tr, // 对应中文:命名发言者
() { () {
Get.bottomSheet( Get.bottomSheet(
const SpeakerAllBottomSheet(), const SpeakerAllBottomSheet(),
@ -304,7 +304,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
width: 150.w, width: 150.w,
child: _button( child: _button(
Icons.auto_awesome, Icons.auto_awesome,
'生成', 'generate'.tr, // 对应中文:生成
() { () {
Get.bottomSheet( Get.bottomSheet(
const GenerateBottomSheet(), const GenerateBottomSheet(),

24
lib/modules/meeting/views/tabs/summary_tab.dart

@ -23,9 +23,9 @@ class _SummaryTabState extends State<SummaryTab> {
late final Worker _personnelListener; late final Worker _personnelListener;
final String _addressPrefix = '地点:'; final String _addressPrefix = '${'location'.tr}:'; // 对应中文:地点
String _addressText = ''; String _addressText = '';
final String _personnelPrefix = '与会人员:'; final String _personnelPrefix = '${'attendees'.tr}:'; // 对应中文:与会人员
String _personnelText = ''; String _personnelText = '';
final TextEditingController _addressController = TextEditingController(); final TextEditingController _addressController = TextEditingController();
final TextEditingController _personnelController = TextEditingController(); final TextEditingController _personnelController = TextEditingController();
@ -57,11 +57,11 @@ class _SummaryTabState extends State<SummaryTab> {
_addressController.text = _addressPrefix + _addressController.text = _addressPrefix +
(_controller.meetingDetails.value.address.isNotEmpty (_controller.meetingDetails.value.address.isNotEmpty
? _controller.meetingDetails.value.address ? _controller.meetingDetails.value.address
: '[输入地点]'); : '[${'inputLocation'.tr}]'); // 对应中文:输入地点
_personnelController.text = _personnelPrefix + _personnelController.text = _personnelPrefix +
(_controller.meetingDetails.value.personnel.isNotEmpty (_controller.meetingDetails.value.personnel.isNotEmpty
? _controller.meetingDetails.value.personnel ? _controller.meetingDetails.value.personnel
: '[输入与会人员]'); : '[${'inputAttendees'.tr}]'); // 对应中文:输入与会人员
final markdown = const ParchmentMarkdownCodec() final markdown = const ParchmentMarkdownCodec()
.decode(_controller.meetingDetails.value.summary); .decode(_controller.meetingDetails.value.summary);
setState(() { setState(() {
@ -288,7 +288,7 @@ class _SummaryTabState extends State<SummaryTab> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'日期和时间:${DateUtil.formatDateMs( '${'dateAndTime'.tr}:${DateUtil.formatDateMs(
_controller.meetingData.value.creationtime, _controller.meetingData.value.creationtime,
format: DateFormats.y_mo_d_h_m, format: DateFormats.y_mo_d_h_m,
)}', )}',
@ -346,12 +346,13 @@ class _SummaryTabState extends State<SummaryTab> {
if (_controller.meetingDetails.value.tasktype >= 3) if (_controller.meetingDetails.value.tasktype >= 3)
_button( _button(
Icons.supervisor_account, Icons.supervisor_account,
'编辑发言者', 'editSpeaker'.tr, // 对应中文:编辑发言者
() { () {
Get.bottomSheet( Get.bottomSheet(
const SpeakerAllBottomSheet(), const SpeakerAllBottomSheet(),
isScrollControlled: true, isScrollControlled: true,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[100],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r), top: Radius.circular(12.r),
@ -368,7 +369,8 @@ class _SummaryTabState extends State<SummaryTab> {
Get.bottomSheet( Get.bottomSheet(
const AIBottomSheet(), const AIBottomSheet(),
isScrollControlled: true, isScrollControlled: true,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.white, backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r), top: Radius.circular(12.r),
@ -382,12 +384,14 @@ class _SummaryTabState extends State<SummaryTab> {
width: 150.w, width: 150.w,
child: _button( child: _button(
Icons.auto_awesome, Icons.auto_awesome,
'生成', 'generate'.tr, // 对应中文:生成
() { () {
Get.bottomSheet( Get.bottomSheet(
const GenerateBottomSheet(), const GenerateBottomSheet(),
isScrollControlled: true, isScrollControlled: true,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], backgroundColor: isDarkMode
? Colors.grey[800]
: Colors.grey[100],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r), top: Radius.circular(12.r),

6
lib/modules/meeting/views/template/template_view.dart

@ -97,7 +97,7 @@ class _TemplateViewState extends State<TemplateView> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
title: Text( title: Text(
'选择总结模版', 'selectSummaryTemplate'.tr, // 对应中文:选择总结模版
style: TextStyle( style: TextStyle(
fontSize: 17.sp, fontSize: 17.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -135,7 +135,7 @@ class _TemplateViewState extends State<TemplateView> {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'就用这个模版', 'useThisTemplate'.tr, // 对应中文:就用这个模版
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.white, color: Colors.white,
@ -304,7 +304,7 @@ class _TemplateViewState extends State<TemplateView> {
), ),
), ),
child: Text( child: Text(
'上次使用', 'lastUsed'.tr, // 对应中文:上次使用
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12.sp, fontSize: 12.sp,

25
lib/modules/permissions/controllers/permissions_controller.dart

@ -1,4 +1,5 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:io' show Platform; import 'dart:io' show Platform;
@ -16,9 +17,13 @@ class PermissionsController extends GetxController {
// Android版本信息(用于权限组显示判断) // Android版本信息(用于权限组显示判断)
final RxInt androidSdkVersion = 0.obs; final RxInt androidSdkVersion = 0.obs;
final GetStorage _storage = GetStorage();
bool? _isCalendarFullAccess;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_isCalendarFullAccess = _storage.read('record_permission_calendar');
_initAndroidVersion(); _initAndroidVersion();
checkAllPermissions(); checkAllPermissions();
} }
@ -68,7 +73,19 @@ class PermissionsController extends GetxController {
for (final permission in permissions) { for (final permission in permissions) {
try { try {
statuses[permission] = await permission.status; if (Platform.isIOS &&
permission == Permission.calendarFullAccess &&
_isCalendarFullAccess == true) {
PermissionStatus calendarStatus = await permission.status;
if (calendarStatus == PermissionStatus.denied) {
statuses[permission] = PermissionStatus.granted;
} else {
statuses[permission] = await permission.status;
_storage.write('record_permission_calendar', false);
}
} else {
statuses[permission] = await permission.status;
}
print('Permission: $permission, Status: ${statuses[permission]}'); print('Permission: $permission, Status: ${statuses[permission]}');
} catch (e) { } catch (e) {
// 某些权限在特定平台可能不支持,设置为拒绝状态 // 某些权限在特定平台可能不支持,设置为拒绝状态
@ -171,6 +188,12 @@ class PermissionsController extends GetxController {
if (hasPermission) { if (hasPermission) {
_showSuccessMessage(); _showSuccessMessage();
if (Platform.isIOS && permission == Permission.calendarFullAccess) {
if (_isCalendarFullAccess == null) {
_storage.write('record_permission_calendar', true);
_isCalendarFullAccess = true;
}
}
} }
// 刷新权限状态 // 刷新权限状态

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

@ -515,27 +515,27 @@ class SettingsView extends GetView<SettingsController> {
// ? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]), // : Colors.grey[200]),
//BLE测试 //BLE测试
// _buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
// title: 'opus解码测试', title: 'opus解码测试',
// subtitle: '测试opus解码', subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching, icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3) ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!, : Colors.green[100]!,
// iconColor: iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!, isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null, titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null, subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () { onTap: () {
// Get.toNamed(Routes.opusTest); Get.toNamed(Routes.opusTest);
// }, },
// isDarkMode: isDarkMode, isDarkMode: isDarkMode,
// ), ),
// Divider( Divider(
// height: 1, height: 1,
// color: isDarkMode color: isDarkMode
// ? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]), : Colors.grey[200]),
// BLE测试 // BLE测试
// _buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(

7
lib/modules/splash/controllers/splash_controller.dart

@ -21,6 +21,8 @@ class SplashController extends GetxController {
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
static const String _tag = 'SplashCtrl'; static const String _tag = 'SplashCtrl';
final versionService = VersionUpdateService.to;
// 进度条动画控制 // 进度条动画控制
final _loadingProgress = 0.0.obs; final _loadingProgress = 0.0.obs;
double get loadingProgress => _loadingProgress.value; double get loadingProgress => _loadingProgress.value;
@ -259,7 +261,10 @@ class SplashController extends GetxController {
/// 异步获取登录Token /// 异步获取登录Token
Future<bool> getLoginToken() async { Future<bool> getLoginToken() async {
try { try {
final response = await Api.tokenlogin(); final appVersion = versionService.cachedAppVersion;
final response = await Api.tokenlogin({
'version': appVersion,
});
if (response?['user'] != null && response['user'] != '') { if (response?['user'] != null && response['user'] != '') {
final user = User.fromJson(response); final user = User.fromJson(response);
await _storage.write('user_info', user.toJson()); await _storage.write('user_info', user.toJson());

6
lib/modules/translation/models/translation_models.dart

@ -64,13 +64,13 @@ class TranslationItem {
String get sourceLanguageName { String get sourceLanguageName {
final languageManager = Get.find<LanguageManager>(); final languageManager = Get.find<LanguageManager>();
return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ??
'未知语言'; 'meetingUnknownLanguage'.tr; // 对应中文:未知语言
} }
/// 获取目标语言的中文名称 /// 获取目标语言的中文名称
String get targetLanguageName { String get targetLanguageName {
final languageManager = Get.find<LanguageManager>(); final languageManager = Get.find<LanguageManager>();
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? return languageManager.getChineseNameByAsrCode(targetLanguageCode) ??
'未知语言'; 'meetingUnknownLanguage'.tr; // 对应中文:未知语言
} }
} }

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

@ -131,6 +131,9 @@ object AgentService : CoroutineScope {
// 语音识别模式 // 语音识别模式
private var currentRecognitionMode = "normal" // 当前识别模式:normal, ble_wakeup, phone_call, push_to_talk private var currentRecognitionMode = "normal" // 当前识别模式:normal, ble_wakeup, phone_call, push_to_talk
//是否播放音乐中
private var isplaymusic = false;
// 打断防抖相关 // 打断防抖相关
private var lastInterruptTime = 0L private var lastInterruptTime = 0L
private val INTERRUPT_DEBOUNCE_MS = 300L private val INTERRUPT_DEBOUNCE_MS = 300L
@ -358,6 +361,11 @@ object AgentService : CoroutineScope {
setPackage(context.packageName) setPackage(context.packageName)
} }
context.sendBroadcast(intent) context.sendBroadcast(intent)
QQMusicSingleton.getInstance().autopauseMusic { result ->
if (result.isSuccess) {
isplaymusic = true
}
}
} }
TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容 TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容
@ -369,6 +377,10 @@ object AgentService : CoroutineScope {
setPackage(context.packageName) setPackage(context.packageName)
} }
context.sendBroadcast(intent) context.sendBroadcast(intent)
if(isplaymusic){
isplaymusic = false
QQMusicSingleton.getInstance().resumeMusic { }
}
} }
TtsEventType.ERROR -> { TtsEventType.ERROR -> {

2
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -1463,7 +1463,7 @@ extension AgentServiceImpl: BleService.Callback {
} }
func onAudioDataReceived1(data: Data) { func onAudioDataReceived1(data: Data) {
} }
func onWakeupSignalReceived() { func onWakeupSignalReceived() {

5
local_plugins/azure_speech/ios/azure_speech/Package.swift

@ -9,13 +9,16 @@ let package = Package(
], ],
dependencies: [ dependencies: [
// 依赖speech接口模块 // 依赖speech接口模块
.package(path: "../../../speech/ios/speech") .package(path: "../../../speech/ios/speech"),
// 添加对ble_service的依赖
.package(path: "../../../ble_service/ios/ble_service")
], ],
targets: [ targets: [
.target( .target(
name: "azure_speech", name: "azure_speech",
dependencies: [ dependencies: [
.product(name: "speech", package: "speech"), .product(name: "speech", package: "speech"),
.product(name: "ble-service", package: "ble_service"),
"MicrosoftCognitiveServicesSpeech" "MicrosoftCognitiveServicesSpeech"
], ],
path: "Sources", // 修改为包含整个 Sources 目录 path: "Sources", // 修改为包含整个 Sources 目录

153
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift

@ -3,6 +3,7 @@ import AVFoundation
import MicrosoftCognitiveServicesSpeech import MicrosoftCognitiveServicesSpeech
import speech import speech
import os.log import os.log
import Network
// MARK: - 基于微软Azure语音服务的ASR实现 // MARK: - 基于微软Azure语音服务的ASR实现
/** /**
@ -21,6 +22,12 @@ public class AzureAsrHelper: NSObject {
private var recognizer: SPXSpeechRecognizer? private var recognizer: SPXSpeechRecognizer?
private var audioConfig: SPXAudioConfiguration? private var audioConfig: SPXAudioConfiguration?
// 网络监听相关
private var networkMonitor: NWPathMonitor?
private var networkQueue: DispatchQueue?
private var isNetworkAvailable = true
private var continuousCallback: ContinuousRecognizeCallback?
// 状态管理 // 状态管理
private var _isContinuousRecognitionActive = false private var _isContinuousRecognitionActive = false
@ -110,20 +117,93 @@ public class AzureAsrHelper: NSObject {
// 预初始化音频组件 // 预初始化音频组件
preInitializeAudioComponents() preInitializeAudioComponents()
// 初始化网络监听
initNetworkMonitoring()
return true return true
} catch { } catch {
os_log("初始化失败: %{public}@", log: log, type: .error, error.localizedDescription) os_log("初始化失败: %{public}@", log: log, type: .error, error.localizedDescription)
return false return false
} }
} }
/**
* 初始化网络监听
*/
private func initNetworkMonitoring() {
networkQueue = DispatchQueue(label: "NetworkMonitor")
networkMonitor = NWPathMonitor()
networkMonitor?.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
let wasAvailable = self.isNetworkAvailable
self.isNetworkAvailable = (path.status == .satisfied)
if wasAvailable && !self.isNetworkAvailable {
// 网络断开
self.handleNetworkLost()
} else if !wasAvailable && self.isNetworkAvailable {
// 网络恢复
self.handleNetworkAvailable()
}
}
if let queue = networkQueue {
networkMonitor?.start(queue: queue)
}
os_log("网络监听已初始化", log: log, type: .info)
}
/**
* 处理网络可用事件
*/
private func handleNetworkAvailable() {
os_log("网络已恢复", log: log, type: .info)
DispatchQueue.main.async { [weak self] in
// self?.continuousCallback?.onNetworkRestored()
}
}
/**
* 处理网络丢失事件
*/
private func handleNetworkLost() {
os_log("网络连接丢失", log: log, type: .error)
// 立即停止音频处理
stopAudioProcessingImmediately()
DispatchQueue.main.async { [weak self] in
// self?.continuousCallback?.onNetworkError("网络连接已断开")
}
}
/**
* 立即停止音频处理
*/
private func stopAudioProcessingImmediately() {
audioStream?.stopMicrophoneCapture()
if _isContinuousRecognitionActive {
do {
try recognizer?.stopContinuousRecognition()
_isContinuousRecognitionActive = false
os_log("因网络问题强制停止识别", log: log, type: .info)
} catch {
_isContinuousRecognitionActive = false
os_log("强制停止识别失败: %@", log: log, type: .error, error.localizedDescription)
}
}
}
/** /**
* 预初始化音频组件 * 预初始化音频组件
* 在初始化阶段就准备好音频流和相关组件,减少首次启动延迟 * 在初始化阶段就准备好音频流和相关组件,减少首次启动延迟
*/ */
private func preInitializeAudioComponents() { private func preInitializeAudioComponents() {
if audioStream == nil { if audioStream == nil {
audioStream = SimpleAudioReceiver(parentHelper: self) audioStream = SimpleAudioReceiver()
audioStream?.initAudioRecord() audioStream?.initAudioRecord()
} }
@ -149,7 +229,11 @@ public class AzureAsrHelper: NSObject {
) -> Bool { ) -> Bool {
guard speechConfig != nil else { guard speechConfig != nil else {
print("语音服务未初始化") print("语音服务未初始化")
//callback.onError("语音服务未初始化") return false
}
// 检查网络状态
if !checkNetworkStatus() {
return false return false
} }
@ -170,6 +254,8 @@ public class AzureAsrHelper: NSObject {
} }
return false return false
} }
print("startContinuousRecognition:\(audioSourceType)")
// 启动音频处理 // 启动音频处理
audioStream?.startAudioRecord(audioSourceType: audioSourceType == .microphone ? .microphone : .external, audioDataCallback: audioDataCallback) audioStream?.startAudioRecord(audioSourceType: audioSourceType == .microphone ? .microphone : .external, audioDataCallback: audioDataCallback)
@ -381,7 +467,7 @@ public class AzureAsrHelper: NSObject {
* 设置麦克风音频流 * 设置麦克风音频流
*/ */
private func setupMicrophoneStream() { private func setupMicrophoneStream() {
audioStream = SimpleAudioReceiver(parentHelper: self) audioStream = SimpleAudioReceiver()
// 【优化】检查音频配置是否已存在 // 【优化】检查音频配置是否已存在
if audioConfig == nil, let pushStream = audioStream?.pushAudioStream { if audioConfig == nil, let pushStream = audioStream?.pushAudioStream {
audioConfig = SPXAudioConfiguration(streamInput: pushStream) audioConfig = SPXAudioConfiguration(streamInput: pushStream)
@ -462,15 +548,19 @@ public class AzureAsrHelper: NSObject {
/** /**
* 设置事件监听器 * 设置事件监听器
*/ */
public func setupEventListeners(callback: ContinuousRecognizeCallback) -> Bool{ public func setupEventListeners(callback: ContinuousRecognizeCallback) -> Bool {
print("设置ssssss监听器:${speechConfig}") print("设置ssssss监听器:${speechConfig}")
// 重设识别器 // 保存回调引用用于网络事件
self.continuousCallback = callback
// 重设识别器
if (!setupRecognizer()) { if (!setupRecognizer()) {
return false return false
} }
guard let recognizer = recognizer else { return false} guard let recognizer = recognizer else { return false }
// 正在识别事件
// 正在识别事件
recognizer.addRecognizingEventHandler { [weak self] (_, event) in recognizer.addRecognizingEventHandler { [weak self] (_, event) in
guard let self = self else { return } guard let self = self else { return }
let result = event.result let result = event.result
@ -532,7 +622,12 @@ public class AzureAsrHelper: NSObject {
os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails) os_log("识别取消: %{public}@", log: self.log, type: .error, errorDetails)
callback.onCanceled(reason, errorDetails) // 检查是否为网络相关错误
if self.isNetworkRelatedError(errorDetails) {
callback.onError("网络连接问题: \(errorDetails)")
} else {
callback.onCanceled(reason, errorDetails)
}
} }
return true return true
} }
@ -575,7 +670,7 @@ public class AzureAsrHelper: NSObject {
if audioStream == nil { if audioStream == nil {
// 创建外部音频拉流对象 // 创建外部音频拉流对象
audioStream = SimpleAudioReceiver(parentHelper: self) audioStream = SimpleAudioReceiver()
audioStream?.initAudioRecord() audioStream?.initAudioRecord()
} }
@ -702,6 +797,43 @@ public class AzureAsrHelper: NSObject {
*/ */
func onError(_ error: String) func onError(_ error: String)
} }
/**
* 检查网络状态
* @return 网络是否可用
*/
private func checkNetworkStatus() -> Bool {
guard let monitor = networkMonitor else {
os_log("网络监听器未初始化,假设网络可用", log: log)
return true
}
let currentPath = monitor.currentPath
let isAvailable = currentPath.status == .satisfied
if !isAvailable {
os_log("网络不可用,无法启动语音识别", log: log, type: .error)
continuousCallback?.onError("网络连接不可用,请检查网络设置")
}
return isAvailable
}
/**
* 判断是否为网络相关错误
* @param errorDetails 错误详情
* @return 是否为网络错误
*/
private func isNetworkRelatedError(_ errorDetails: String) -> Bool {
let networkErrorKeywords = [
"network", "connection", "timeout", "unreachable",
"网络", "连接", "超时", "不可达",
"ConnectionFailure", "NetworkError", "TimeoutError"
]
let lowercaseError = errorDetails.lowercased()
return networkErrorKeywords.contains { lowercaseError.contains($0.lowercased()) }
}
} }
// //ios 语言检测 // //ios 语言检测
@ -754,3 +886,4 @@ public class AzureAsrHelper: NSObject {
// "originalCode": topResult.language // "originalCode": topResult.language
// ] // ]
// } // }

582
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -5,6 +5,8 @@ import MicrosoftCognitiveServicesSpeech
// 自定义语音处理组件,提供音频流处理等功能 // 自定义语音处理组件,提供音频流处理等功能
import speech import speech
import os.log import os.log
// 导入蓝牙服务模块,用于处理蓝牙设备回调
import ble_service
/** /**
* Azure Speech Plugin * Azure Speech Plugin
@ -32,6 +34,14 @@ import os.log
// 是否已添加TTS事件监听器 // 是否已添加TTS事件监听器
private var isTtsListenerAdded = false private var isTtsListenerAdded = false
// AST相关
private var astChannel: FlutterMethodChannel?
private var astEventChannel: FlutterEventChannel?
private let azureAstHelper = IntegratedSpeechTranslationService()
// 创建事件回调
private var astEventCallback: AstEventCallback?
// 是否已添加AST事件监听器
private var isAstListenerAdded = false
// 当前的连续识别回调 // 当前的连续识别回调
private var currentAsrCallback: AsrCallbackWrapper? private var currentAsrCallback: AsrCallbackWrapper?
@ -39,7 +49,9 @@ import os.log
// 插件注册 // 插件注册
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let instance = AzureSpeechPlugin() let instance = AzureSpeechPlugin()
// 2. 初始化BleService并注册回调
BleService.shared.addCallback(instance)
// 初始化ASR通道 // 初始化ASR通道
let asrChannel = FlutterMethodChannel(name: "azure_speech/asr", binaryMessenger: registrar.messenger()) let asrChannel = FlutterMethodChannel(name: "azure_speech/asr", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: asrChannel) registrar.addMethodCallDelegate(instance, channel: asrChannel)
@ -60,6 +72,17 @@ import os.log
instance?.handleTtsMethodCall(call, result: result) instance?.handleTtsMethodCall(call, result: result)
} }
// 初始化AST通道
let astChannel = FlutterMethodChannel(name: "azure_speech/ast", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: astChannel)
instance.astChannel = astChannel
// 设置AST通道处理器 - 使用实例方法
astChannel.setMethodCallHandler { [weak instance] (call, result) in
instance?.handleAstMethodCall(call, result: result)
}
// 初始化ASR事件通道 // 初始化ASR事件通道
let asrEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) let asrEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger())
asrEventChannel.setStreamHandler(instance) asrEventChannel.setStreamHandler(instance)
@ -69,6 +92,14 @@ import os.log
let ttsEventChannel = FlutterEventChannel(name: "azure_speech/tts_events", binaryMessenger: registrar.messenger()) let ttsEventChannel = FlutterEventChannel(name: "azure_speech/tts_events", binaryMessenger: registrar.messenger())
ttsEventChannel.setStreamHandler(instance) ttsEventChannel.setStreamHandler(instance)
instance.ttsEventChannel = ttsEventChannel instance.ttsEventChannel = ttsEventChannel
// 初始化TTS事件通道
let astEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger())
astEventChannel.setStreamHandler(instance)
instance.astEventChannel = astEventChannel
} }
// 发送ASR事件方法 // 发送ASR事件方法
internal func sendAsrEvent(_ event: [String: Any]) { internal func sendAsrEvent(_ event: [String: Any]) {
@ -153,20 +184,21 @@ import os.log
return return
} }
// 解析音频源类型 // 解析音频源类型参数
let audioSourceType: AzureAsrHelper.AudioSourceType guard let args = call.arguments as? [String: Any],
if let args = call.arguments as? [String: Any], let isExternalActive = args["audioSourceType"] as? Bool else {
let audioSourceString = args["audioSourceType"] as? String { result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil))
switch audioSourceString.lowercased() { return
case "external":
audioSourceType = .external
default:
audioSourceType = .microphone
}
} else {
audioSourceType = .microphone
} }
// 根据参数确定音频源类型
let audioSourceType = isExternalActive ?
AzureAsrHelper.AudioSourceType.external :
AzureAsrHelper.AudioSourceType.microphone
print("startContinuousRecognition: \(audioSourceType)")
// 启动连续识别
let success = azureAsrHelper.startContinuousRecognition( let success = azureAsrHelper.startContinuousRecognition(
audioSourceType: audioSourceType audioSourceType: audioSourceType
) )
@ -184,17 +216,17 @@ import os.log
callback: currentAsrCallback! callback: currentAsrCallback!
) )
result(success) result(success)
case "disableBluetoothAudio": case "disableBluetoothAudio":
azureAsrHelper.disableBluetoothAudio(); azureAsrHelper.disableBluetoothAudio();
return return
case "restoreOriginalAudioState": case "restoreOriginalAudioState":
azureAsrHelper.restoreOriginalAudioState(); azureAsrHelper.restoreOriginalAudioState();
return return
case "stopContinuousRecognition": case "stopContinuousRecognition":
let success = azureAsrHelper.stopContinuousRecognition() let success = azureAsrHelper.stopContinuousRecognition()
currentAsrCallback = nil currentAsrCallback = nil
result(success) result(success)
@ -287,15 +319,15 @@ import os.log
} catch { } catch {
result(FlutterError(code: "ENABLERECORD_ERROR", message: error.localizedDescription, details: nil)) result(FlutterError(code: "ENABLERECORD_ERROR", message: error.localizedDescription, details: nil))
} }
case "pauseRecord": case "pauseRecord":
azureAsrHelper.pauseRecord() azureAsrHelper.pauseRecord()
result(true) result(true)
// case "resumeRecord": // case "resumeRecord":
// azureAsrHelper.resumeRecord() // azureAsrHelper.resumeRecord()
// result(true) // result(true)
case "stopRecord": case "stopRecord":
guard let args = call.arguments as? [String: Any], guard let args = call.arguments as? [String: Any],
@ -311,16 +343,16 @@ import os.log
} }
case "disableBluetoothAudio": case "disableBluetoothAudio":
print("disableBluetoothAudio") print("disableBluetoothAudio")
azureAsrHelper.disableBluetoothAudio(); azureAsrHelper.disableBluetoothAudio();
azureTtsHelper.stop() azureTtsHelper.stop()
result(true) result(true)
case "restoreOriginalAudioState": case "restoreOriginalAudioState":
print("restoreOriginalAudioState") print("restoreOriginalAudioState")
azureAsrHelper.restoreOriginalAudioState(); azureAsrHelper.restoreOriginalAudioState();
azureTtsHelper.stop() azureTtsHelper.stop()
result(true) result(true)
case "setAudioConfig": case "setAudioConfig":
@ -415,13 +447,13 @@ import os.log
if (type == 0) { if (type == 0) {
// 默认(如果有耳机选耳机,否则使用系统扬声器) // 默认(如果有耳机选耳机,否则使用系统扬声器)
azureAsrHelper.restoreOriginalAudioState(); azureAsrHelper.restoreOriginalAudioState();
// azureTtsHelper.setAudioOutputDevice() // azureTtsHelper.setAudioOutputDevice()
} else if (type == 1) { } else if (type == 1) {
// 强制使用声器 // 强制使用声器
azureAsrHelper.disableBluetoothAudio(); azureAsrHelper.disableBluetoothAudio();
// azureTtsHelper.setAudioOutputDevice() // azureTtsHelper.setAudioOutputDevice()
} else if (type == 2) { } else if (type == 2) {
// 强制使用耳机 // 强制使用耳机
@ -437,60 +469,388 @@ import os.log
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }
} }
// MARK: - AST 方法处理
private func handleAstMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "enableRecord":
guard let args = call.arguments as? [String: Any],
let filePath = args["filePath"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing filePath", details: nil))
return
}
do {
os_log("音频文件名称为: %@", log: log, type: .info, filePath)
try azureAstHelper.enableRecord(filePath: filePath)
result(true)
} catch {
result(FlutterError(code: "ENABLERECORD_ERROR", message: error.localizedDescription, details: nil))
}
case "startContinuousTranslation":
do {
os_log("开启翻译", log: log, type: .info)
try azureAstHelper.startContinuousTranslation()
result(true)
} catch {
result(FlutterError(code: "START_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil))
}
case "stopContinuousTranslation":
do {
os_log("停止翻译", log: log, type: .info)
try azureAstHelper.stopContinuousTranslation()
result(true)
} catch {
result(FlutterError(code: "STOP_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil))
}
case "dispose":
do {
os_log("释放AST资源", log: log, type: .info)
azureAstHelper.dispose()
result(true)
} catch {
result(FlutterError(code: "DISPOSE_ERROR", message: error.localizedDescription, details: nil))
}
case "recognizeCallback":
os_log("recognizeCallback:", log: log, type: .info)
result(true)
case "path":
guard let args = call.arguments as? [String: Any],
let filePath = args["filePath"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing filePath", details: nil))
return
}
let fileURL = URL(fileURLWithPath: filePath)
// if FileManager.default.fileExists(atPath: filePath) {
// // 启动异步任务来按播放速度读取音频文件
// Task {
// // await streamAudioFile(fileURL: fileURL)
// }
// result(true)
// } else {
// result(FlutterError(code: "FILE_NOT_FOUND", message: "音频文件不存在: \(filePath)", details: nil))
// }
case "initialize":
guard let args = call.arguments as? [String: Any] else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing arguments", details: nil))
return
}
let subscriptionKey = args["subscriptionKey"] as? String ?? ""
let region = args["region"] as? String ?? ""
let supportedLanguages = args["supportedLanguages"] as? [String] ?? ["zh-CN"]
let useExternalAudio = args["useExternalAudio"] as? Bool ?? false
// 获取翻译服务配置参数
let translationAccessKey = args["translationAccessKey"] as? String ?? ""
let translationSecretKey = args["translationSecretKey"] as? String ?? ""
let translationRegion = args["translationRegion"] as? String ?? "cn-north-1"
os_log("初始化AST服务", log: log, type: .info)
// 创建Azure配置
let azureConfig = AzureConfiguration(
subscriptionKey: subscriptionKey,
region: region
)
// 创建翻译配置
let translationConfig = TranslationConfiguration(
accessKey: translationAccessKey,
secretKey: translationSecretKey,
region: translationRegion
)
// 创建服务配置
let serviceConfig = IntegratedSpeechTranslationService.ServiceConfiguration(
sourceLanguage: supportedLanguages.first ?? "zh-CN",
targetLanguage: supportedLanguages.count > 1 ? supportedLanguages[1] : "en-US"
)
// 创建事件回调
let callback = AstEventCallback(plugin: self)
astEventCallback = callback
// 使用异步任务调用初始化方法
Task {
await azureAstHelper.initialize(
azureConfig: azureConfig,
translationConfig: translationConfig,
serviceConfig: serviceConfig,
callback: callback
)
}
result(true)
default:
result(FlutterMethodNotImplemented)
}
}
} }
// MARK: - AST Event Callback
// MARK: - ASR回调包装器 /**
private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback { * AST事件回调类
* 实现 IntegratedSpeechTranslationService.ServiceEventCallback 协议
*/
private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventCallback {
private weak var plugin: AzureSpeechPlugin? private weak var plugin: AzureSpeechPlugin?
/**
* 构造函数
* @param plugin AzureSpeechPlugin 实例的弱引用
*/
init(plugin: AzureSpeechPlugin) { init(plugin: AzureSpeechPlugin) {
self.plugin = plugin self.plugin = plugin
} }
func onResult(_ text: String, _ detectedLanguage: String) { /**
plugin?.sendAsrEvent([ * 服务初始化完成回调
"type": "result", */
"text": text, public func onServiceInitialized() {
"detectedLanguage": detectedLanguage // plugin?.sendAsrEvent([
]) // "type": "serviceInitialized"
// ])
} }
func onRecognizing(_ recognizing: String, _ detectedLanguage: String) { /**
plugin?.sendAsrEvent([ * 识别中回调
"type": "recognizing", * @param text 正在识别的文本
"text": recognizing, * @param language 语言
"detectedLanguage": detectedLanguage * @param confidence 置信度
]) */
public func onRecognizing(text: String, language: String, confidence: Float) {
// plugin?.sendAsrEvent([
// "type": "recognizing",
// "text": text,
// "language": language,
// "confidence": confidence
// ])
} }
func onSessionStarted() { /**
plugin?.sendAsrEvent([ * 识别完成回调
"type": "sessionStarted" * @param text 识别的文本
]) * @param language 语言
* @param confidence 置信度
*/
public func onRecognized(text: String, language: String, confidence: Float) {
print( "识别到文本: \(text), 语言: \(language), 置信度: \(confidence)")
plugin?.sendAsrEvent([
"type": "result1",
"text": text,
"language": language,
])
} }
func onSessionStopped() { /**
plugin?.sendAsrEvent([ * 翻译完成回调
"type": "sessionStopped" * @param originalText 原始文本
]) * @param translatedText 翻译文本
* @param targetLanguage 目标语言
*/
public func onTranslated(originalText: String, translatedText: String, targetLanguage: String) {
// plugin?.sendAsrEvent([
// "type": "translated",
// "originalText": originalText,
// "translatedText": translatedText,
// "targetLanguage": targetLanguage
// ])
} }
func onCanceled(_ reason: String, _ errorDetails: String) { /**
plugin?.sendAsrEvent([ * 翻译开始回调
"type": "canceled", * @param text 要翻译的文本
"reason": reason, */
"errorDetails": errorDetails public func onTranslationStarted(text: String) {
]) // plugin?.sendAsrEvent([
// "type": "translationStarted",
// "text": text
// ])
} }
func onError(_ error: String) { /**
plugin?.sendAsrEvent([ * 翻译失败回调
"type": "error", * @param text 翻译失败的文本
"error": error * @param error 错误信息
]) */
public func onTranslationFailed(text: String, error: String) {
// plugin?.sendAsrEvent([
// "type": "translationFailed",
// "text": text,
// "error": error
// ])
}
/**
* 语音合成开始回调
* @param text 要合成的文本
*/
func onSynthesisStarted(text: String) {
// plugin?.sendAsrEvent([
// "type": "synthesisStarted",
// "text": text
// ])
}
/**
* 语音合成完成回调
* @param text 合成的文本
*/
public func onSynthesisCompleted(text: String) {
// plugin?.sendAsrEvent([
// "type": "synthesisCompleted",
// "text": text
// ])
}
/**
* 语音合成失败回调
* @param text 合成失败的文本
* @param error 错误信息
*/
public func onSynthesisFailed(text: String, error: String) {
// plugin?.sendAsrEvent([
// "type": "synthesisFailed",
// "text": text,
// "error": error
// ])
}
/**
* 语音合成进度回调
* @param text 正在合成的文本
* @param progress 进度(0.0-1.0)
*/
public func onSynthesisProgress(text: String, progress: Float) {
// plugin?.sendAsrEvent([
// "type": "synthesisProgress",
// "text": text,
// "progress": progress
// ])
}
/**
* 识别开始回调
*/
func onRecognitionStarted() {
// plugin?.sendAsrEvent([
// "type": "recognitionStarted"
// ])
}
/**
* 识别停止回调
*/
public func onRecognitionStopped() {
// plugin?.sendAsrEvent([
// "type": "recognitionStopped"
// ])
}
/**
* 状态变化回调
* @param component 组件名称
* @param isActive 是否激活
*/
public func onStateChanged(component: String, isActive: Bool) {
// plugin?.sendAsrEvent([
// "type": "stateChanged",
// "component": component,
// "isActive": isActive
// ])
}
/**
* 错误回调
* @param component 组件名称
* @param error 错误信息
*/
public func onError(component: String, error: String) {
// plugin?.sendAsrEvent([
// "type": "error",
// "component": component,
// "error": error
// ])
}
/**
* 语音合成音频数据生成回调
* @param text 合成的文本
* @param audioData 音频数据
*/
public func onSynthesisAudioGenerated(text: String, audioData: Data) {
print("语音合成音频数据生成回调", text, audioData.count)
// 调用BLE服务写入音频数据
BleService.shared.writeExternalAudioData(data: audioData)
// plugin?.sendAsrEvent([
// "type": "synthesisAudioGenerated",
// "text": text,
// "audioDataSize": audioData.count
// ])
} }
} }
extension AzureSpeechPlugin: BleService.Callback {
/// 扫描结果回调
/// - Parameter devices: 扫描到的设备列表
public func onScanResult(devices: [[String: Any]]) {
}
/// 连接状态变化回调
/// - Parameter state: 连接状态
public func onConnectionStateChanged(state: Int) {
}
/// 接收到音频数据回调
/// - Parameter data: 音频数据
public func onAudioDataReceived(data: Data) {
// 安全解包版本
guard let audioStream = azureAsrHelper.audioStream else {
os_log("音频流未初始化", type: .error)
return
}
audioStream.saveAudioDataTo(data: data)
}
/// 接收到音频数据回调1
/// - Parameter data: 音频数据
public func onAudioDataReceived1(data: Data) {
// 安全解包版本
guard let audioStream = azureAstHelper.audioProcessor else {
os_log("音频流未初始化", type: .error)
return
}
audioStream.saveAudioDataTo(data: data)
}
/// 接收到编码数据回调
/// - Parameter data: 编码数据
public func onEncodedDataReceived(data: Data) {
}
/// 唤醒信号接收回调
public func onWakeupSignalReceived() {
}
/// 设备信息接收回调
/// - Parameters:
/// - infoType: 信息类型
/// - infoData: 信息数据
public func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) {
}
}
// 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? {
@ -596,6 +956,7 @@ extension AzureSpeechPlugin: AudioDataListener {
} }
} }
// MARK: - 音频数据回调实现 // MARK: - 音频数据回调实现
/** /**
* 音频数据回调实现类 * 音频数据回调实现类
@ -621,4 +982,89 @@ private class AudioDataCallbackImpl: SimpleAudioReceiver.AudioDataCallback {
} }
} }
// MARK: - ASR Callback Wrapper
/**
* ASR回调包装器类
* 实现 AzureAsrHelper.ContinuousRecognizeCallback 协议
*/
private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback {
private weak var plugin: AzureSpeechPlugin?
/**
* 初始化ASR回调包装器
* @param plugin AzureSpeechPlugin实例
*/
init(plugin: AzureSpeechPlugin) {
self.plugin = plugin
}
/**
* 识别结果回调
* @param text 识别的文本
* @param detectedLanguage 检测到的语言
*/
func onResult(_ text: String, _ detectedLanguage: String) {
plugin?.sendAsrEvent([
"type": "result",
"text": text,
"detectedLanguage": detectedLanguage
])
}
/**
* 识别中回调
* @param text 正在识别的文本
* @param detectedLanguage 检测到的语言
*/
func onRecognizing(_ text: String, _ detectedLanguage: String) {
plugin?.sendAsrEvent([
"type": "recognizing",
"text": text,
"detectedLanguage": detectedLanguage
])
}
/**
* 会话开始回调
*/
func onSessionStarted() {
plugin?.sendAsrEvent([
"type": "sessionStarted"
])
}
/**
* 会话结束回调
*/
func onSessionStopped() {
plugin?.sendAsrEvent([
"type": "sessionStopped"
])
}
/**
* 识别取消回调
* @param reason 取消原因
* @param errorDetails 错误详情
*/
func onCanceled(_ reason: String, _ errorDetails: String) {
plugin?.sendAsrEvent([
"type": "canceled",
"reason": reason,
"errorDetails": errorDetails
])
}
/**
* 错误回调
* @param error 错误信息
*/
func onError(_ error: String) {
plugin?.sendAsrEvent([
"type": "error",
"error": error
])
}
}

80
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift

@ -44,7 +44,7 @@ import os.log
private var translationService: TranslationServiceInterface? private var translationService: TranslationServiceInterface?
// 音频处理 // 音频处理
private var audioProcessor: SimpleAudioReceiver? public var audioProcessor: SimpleAudioReceiver?
private var audioConfig: SPXAudioConfiguration? private var audioConfig: SPXAudioConfiguration?
// 录音文件处理 // 录音文件处理
@ -333,7 +333,7 @@ import os.log
* 初始化音频处理器 * 初始化音频处理器
*/ */
private func initializeAudioProcessor() { private func initializeAudioProcessor() {
audioProcessor = SimpleAudioReceiver(parentHelper: self as! AzureAsrHelper) audioProcessor = SimpleAudioReceiver()
audioProcessor?.initAudioRecord() audioProcessor?.initAudioRecord()
// 检查音频配置是否已存在 // 检查音频配置是否已存在
if audioConfig == nil, let pushStream = audioProcessor?.pushAudioStream { if audioConfig == nil, let pushStream = audioProcessor?.pushAudioStream {
@ -486,16 +486,24 @@ import os.log
} }
} }
// 合成进行中事件 // 合成进行中事件 - 修改为返回音频数据
synthesizer?.addSynthesizingEventHandler { [weak self] (synthesizer, event) in synthesizer?.addSynthesizingEventHandler { [weak self] (synthesizer, event) in
guard let self = self else { return } guard let self = self else { return }
os_log("合成进行中事件", log: self.log, type: .debug) // 安全解包audioData
// 计算进度(简化版) guard let audioData = event.result.audioData else {
let progress: Float = 0.5 os_log("合成进行中事件,音频数据为空", log: self.log, type: .debug)
return
}
os_log("合成进行中事件,音频数据长度: %d bytes", log: self.log, type: .debug, audioData.count)
DispatchQueue.main.async { DispatchQueue.main.async {
self.eventCallback?.onSynthesisProgress(text: "", progress: progress)
// 立即返回当前生成的音频数据
self.eventCallback?.onSynthesisAudioGenerated(text: "", audioData: audioData)
} }
} }
@ -506,6 +514,7 @@ import os.log
os_log("合成完成事件", log: self.log, type: .info) os_log("合成完成事件", log: self.log, type: .info)
DispatchQueue.main.async { DispatchQueue.main.async {
self.serviceState.isSynthesizing = false
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false) self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
} }
} }
@ -654,10 +663,8 @@ import os.log
} }
// 进行语音合成 // 进行语音合成
if let audioData = synthesizeText(translatedText) { if synthesizeTextStreaming(translatedText) {
await MainActor.run { print("语音合成已启动")
eventCallback?.onSynthesisAudioGenerated(text: translatedText, audioData: audioData)
}
} }
} else { } else {
let errorMessage = translationResult?.error ?? "翻译失败" let errorMessage = translationResult?.error ?? "翻译失败"
@ -688,54 +695,34 @@ import os.log
/** /**
* 合成文本为音频数据 * 合成文本为音频数据
*/ */
private func synthesizeText(_ text: String) -> Data? { /// 流式语音合成
/// - Parameter text: 要合成的文本
/// - Returns: 合成是否成功启动
private func synthesizeTextStreaming(_ text: String) -> Bool {
guard let synthesizer = synthesizer else { guard let synthesizer = synthesizer else {
os_log("语音合成器未初始化", log: log, type: .error) os_log("语音合成器未初始化", log: log, type: .error)
return nil return false
} }
do { do {
let ssml = buildSSML(text: text) let ssml = buildSSML(text: text)
os_log("开始合成语音: %@", log: log, type: .info, text) os_log("开始流式合成语音: %@", log: log, type: .info, text)
serviceState.isSynthesizing = true serviceState.isSynthesizing = true
DispatchQueue.main.async { DispatchQueue.main.async {
self.eventCallback?.onSynthesisStarted(text: text) self.eventCallback?.onSynthesisStarted(text: text)
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: true) self.eventCallback?.onStateChanged(component: "Synthesis", isActive: true)
} }
try synthesizer.speakSsml(ssml)
let result = try synthesizer.speakSsml(ssml) return true
serviceState.isSynthesizing = false
DispatchQueue.main.async {
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
}
if result.reason == .synthesizingAudioCompleted {
os_log("语音合成成功", log: log, type: .info)
DispatchQueue.main.async {
self.eventCallback?.onSynthesisCompleted(text: text)
}
return result.audioData
} else {
let errorMessage = "合成失败: \(result.reason)"
os_log("%@", log: log, type: .error, errorMessage)
DispatchQueue.main.async {
self.eventCallback?.onSynthesisFailed(text: text, error: errorMessage)
}
return nil
}
} catch { } catch {
serviceState.isSynthesizing = false serviceState.isSynthesizing = false
let errorMessage = "合成异常: \(error.localizedDescription)" let errorMessage = "合成启动异常: \(error.localizedDescription)"
os_log("%@", log: log, type: .error, errorMessage) os_log("%@", log: log, type: .error, errorMessage)
DispatchQueue.main.async { DispatchQueue.main.async {
@ -743,10 +730,17 @@ import os.log
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false) self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
} }
return nil return false
} }
} }
/// 保留原有的同步合成方法作为备用
private func synthesizeText(_ text: String) -> Data? {
// 现在调用流式合成方法
let success = synthesizeTextStreaming(text)
return success ? Data() : nil // 返回空数据表示已启动,实际数据通过回调返回
}
/** /**
* 构建SSML * 构建SSML
*/ */

12
local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift

@ -38,14 +38,8 @@ public class SimpleAudioReceiver: NSObject {
private var currentRoute: AudioOutputRoute? private var currentRoute: AudioOutputRoute?
//public var onAudioData: ((Data) -> Void)? //public var onAudioData: ((Data) -> Void)?
public var recordfile: RecordFile? public var recordfile: RecordFile?
// 添加对外部类的弱引用
private weak var parentHelper: AzureAsrHelper?
// 添加初始化方法,接收外部类引用
init(parentHelper: AzureAsrHelper) {
self.parentHelper = parentHelper
super.init()
}
/** /**
* 初始化 * 初始化
*/ */
@ -170,7 +164,7 @@ public class SimpleAudioReceiver: NSObject {
*/ */
public func saveAudioDataTo(data: Data) { public func saveAudioDataTo(data: Data) {
// 通过父类引用调用方法 // 通过父类引用调用方法
if audioSourceType != .external || !(parentHelper?.isContinuousRecognitionActive() ?? false) { if audioSourceType != .external {
return return
} }
// print("外部data=\(data)") // print("外部data=\(data)")

27
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/Info.plist

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>JLAudioUnitKit.framework/JLAudioUnitKit</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>JLAudioUnitKit.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

24
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitKit.h

@ -0,0 +1,24 @@
//
// JLAudioUnitKit.h
// JLAudioUnitKit
//
// Created by EzioChan on 2024/11/14.
//
#import <Foundation/Foundation.h>
//! Project version number for JLAudioUnitKit.
FOUNDATION_EXPORT double JLAudioUnitKitVersionNumber;
//! Project version string for JLAudioUnitKit.
FOUNDATION_EXPORT const unsigned char JLAudioUnitKitVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <JLAudioUnitKit/PublicHeader.h>
#import <JLAudioUnitKit/JLSpeexDecoder.h>
#import <JLAudioUnitKit/JLOpusDecoder.h>
#import <JLAudioUnitKit/JLOpusEncoder.h>
#import <JLAudioUnitKit/JLOpusFormat.h>
#import <JLAudioUnitKit/JLPcmToWtg.h>
#import <JLAudioUnitKit/JLPcmToWav.h>
#import <JLAudioUnitKit/JLAudioUnitPlayer.h>

58
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitPlayer.h

@ -0,0 +1,58 @@
//
// JLAudioUnitPlayer.h
// JLAudioUnitKit
//
// Created by EzioChan on 2025/4/16.
// Copyright © 2025 ZhuHai JieLi Technology Co.,Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, JLAudioUnitPlayerType) {
JLAudioUnitPlayerTypeFile, // 支持 MP3/WAV/AAC 等文件格式(AVAudioPlayer)
JLAudioUnitPlayerTypePCM // 支持 PCM 流式播放(Audio Queue)
};
@class JLAudioUnitPlayer;
@protocol JLAudioPlayerDelegate <NSObject>
@optional
// 播放进度更新(秒)
- (void)audioPlayer:(JLAudioUnitPlayer *)player didUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration;
// 播放完成
- (void)audioPlayerDidFinishPlaying:(JLAudioUnitPlayer *)player;
// 播放错误
- (void)audioPlayer:(JLAudioUnitPlayer *)player didFailWithError:(NSError *)error;
@end
@interface JLAudioUnitPlayer : NSObject
@property (nonatomic, weak) id<JLAudioPlayerDelegate> delegate;
@property (nonatomic, readonly) NSTimeInterval duration; // 总时长(文件模式下有效)
@property (nonatomic, readonly) NSTimeInterval currentTime;
@property (nonatomic, readonly) BOOL isPlaying;
#pragma mark - 初始化方法
/// 文件播放初始化(MP3/WAV/AAC)
- (instancetype)initWithAudioFile:(NSString *)filePath;
/// PCM 流播放初始化(需指定格式)
- (instancetype)initWithPCMFormat:(AudioStreamBasicDescription)format;
#pragma mark - 播放控制
- (void)play;
- (void)pause;
- (void)stop;
- (void)seekToTime:(NSTimeInterval)time; // 仅文件模式有效
#pragma mark - PCM 流式输入(仅 PCM 模式有效)
- (void)appendPCMData:(NSData *)pcmData;
- (void)endPCMStream; // 结束 PCM 流输入
@end
NS_ASSUME_NONNULL_END

66
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusDecoder.h

@ -0,0 +1,66 @@
//
// JLOpusDecoder.h
// JLAudioUnitKit
//
// Created by EzioChan on 2024/11/14.
// Copyright © 2024 ZhuHai JieLi Technology Co.,Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <JLAudioUnitKit/JLOpusFormat.h>
NS_ASSUME_NONNULL_BEGIN
@class JLOpusDecoder;
/// Opus 解码代理
@protocol JLOpusDecoderDelegate <NSObject>
/// Opus 数据解码
/// - Parameters:
/// - decoder: 解码器
/// - data: pcm 数据
/// - error: 错误信息
-(void)opusDecoder:(JLOpusDecoder *)decoder Data:(NSData* _Nullable)data error:(NSError* _Nullable)error;
@end
typedef void(^JLOpusDecoderConvertBlock)(NSString *_Nullable pcmPath,NSError *_Nullable error);
/// Opus 解码
@interface JLOpusDecoder : NSObject
/// 数据格式参数
@property (nonatomic, strong) JLOpusFormat *opusFormat;
/// 代理委托
@property(nonatomic, weak) id<JLOpusDecoderDelegate> delegate;
- (instancetype)init NS_UNAVAILABLE;
/// 初始化
/// - Parameters:
/// - format: 解码格式
/// - delegate: 代理
- (instancetype)initDecoder:(JLOpusFormat *)format delegate:(id<JLOpusDecoderDelegate>)delegate;
/// 重置解码格式
/// - Parameter format: 解码格式
-(void)resetOpusFramet:(JLOpusFormat *)format;
/// 输入 Opus 数据
/// - Parameter data: Opus 数据
-(void)opusDecoderInputData:(NSData *)data;
/// 解码文件
/// - Parameters:
/// - input: opus 文件
/// - outPut: 输出路径
/// - result: 结果回调
-(void)opusDecodeFile:(NSString *)input outPut:(NSString *_Nullable)outPut Resoult:(JLOpusDecoderConvertBlock _Nullable)result;
/// 释放
-(void)opusOnRelease;
@end
NS_ASSUME_NONNULL_END

62
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusEncoder.h

@ -0,0 +1,62 @@
//
// JLOpusEncoder.h
// JLAudioUnitKit
//
// Created by EzioChan on 2024/11/14.
// Copyright © 2024 ZhuHai JieLi Technology Co.,Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <JLAudioUnitKit/JLOpusFormat.h>
NS_ASSUME_NONNULL_BEGIN
@class JLOpusEncoder;
/// Opus 编码代理
@protocol JLOpusEncoderDelegate <NSObject>
/// PCM 数据编码
/// - Parameters:
/// - encoder: 解码器
/// - data: opus 数据
/// - error: 错误信息
-(void)opusEncoder:(JLOpusEncoder *)encoder Data:(NSData* _Nullable)data error:(NSError* _Nullable)error;
@end
typedef void(^JLOpusEncoderConvertBlock)(NSString *_Nullable pcmPath,NSError *_Nullable error);
/// Opus 编码
@interface JLOpusEncoder : NSObject
/// 音频格式
@property (nonatomic, strong) JLOpusFormat *opusFormat;
/// 代理
@property (nonatomic, weak) id<JLOpusEncoderDelegate> delegate;
-(instancetype)init NS_UNAVAILABLE;
/// 初始化
/// - Parameters:
/// - format: 音频格式
/// - delegate: 代理
-(instancetype)initFormat:(JLOpusFormat *)format delegate:(id<JLOpusEncoderDelegate>)delegate;
/// PCM 数据
/// - Parameter data: PCM 数据
-(void)opusEncodeData:(NSData *)data;
/// PCM 文件转换成 Opus 文件
/// - Parameters:
/// - pcmPath: PCM 文件存放路径
/// - outPut: 文件输出路径
/// - result: 结果回调
-(void)opusEncodeFile:(NSString *)pcmPath outPut:(NSString *_Nullable)outPut Resoult:(JLOpusEncoderConvertBlock _Nullable)result;
/// 释放
-(void)opusOnRelease;
@end
NS_ASSUME_NONNULL_END

53
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusFormat.h

@ -0,0 +1,53 @@
//
// JLOpusOptions.h
// JLAudioUnitKit
//
// Created by EzioChan on 2024/11/14.
// Copyright © 2024 ZhuHai JieLi Technology Co.,Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#define OPUS_JL_MAX_FRAME_SIZE 48000 * 2
#define OPUS_JL_MAX_PACKET_SIZE 1500
@interface JLOpusFormat : NSObject
/// 采样率
@property (nonatomic, assign) int sampleRate;
/// 单/双声道
@property (nonatomic, assign) int channels;
/// 帧长度 20ms 默认值
@property (nonatomic, assign) int frameDuration;
/// BitRate
@property (nonatomic, assign) int bitRate;
/// 数据帧大小
@property (nonatomic, assign, readonly) int frameSize;
/// 数据大小
@property (nonatomic, assign) int dataSize;
/// 是否包含数据头部
@property (nonatomic, assign) BOOL hasDataHeader;
-(instancetype)init NS_UNAVAILABLE;
/// 默认配置
/// sampleRate: 16000
/// channels: 1
/// frameDuration: 20
/// dataSize: 40
/// frameSize: 320
/// hasDataHeader: YES
+(JLOpusFormat*)defaultFormats;
@end
NS_ASSUME_NONNULL_END

55
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWav.h

@ -0,0 +1,55 @@
//
// JLPcmToWav.h
// JLAudioUnitKit
//
// Created by EzioChan on 2025/4/15.
// Copyright © 2025 ZhuHai JieLi Technology Co.,Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// PCM 转 WAV
@interface JLPcmToWav : NSObject
#pragma mark - 流式编码接口
/// 初始化流式编码器
/// @param outputPath 输出文件路径
/// @param sampleRate 采样率(如 16000)
/// @param numChannels 声道数(1 或 2)
/// @param bitsPerSample 位深(16)
- (instancetype)initWithOutputPath:(NSString *)outputPath
sampleRate:(uint32_t)sampleRate
numChannels:(uint16_t)numChannels
bitsPerSample:(uint16_t)bitsPerSample;
/// 追加 PCM 数据(需确保数据格式与初始化参数一致)
/// @param pcmData PCM 数据块
/// @param error 错误信息
- (BOOL)appendPCMData:(NSData *)pcmData error:(NSError **)error;
/// 完成编码并关闭文件(必须调用以更新 WAV 头)
/// @param error 错误信息
- (BOOL)finishWithError:(NSError **)error;
#pragma mark - 一次性编码接口(便捷方法)
/// 将 PCM 数据一次性转为 WAV 文件
/// @param pcmData PCM 数据块
/// @param outputPath 输出文件路径
/// @param sampleRate 采样率(如 16000)
/// @param numChannels 声道数(1 或 2)
/// @param bitsPerSample 位深(16)
/// @param error 错误信息
+ (BOOL)convertPCMData:(NSData *)pcmData
toWAVFile:(NSString *)outputPath
sampleRate:(uint32_t)sampleRate
numChannels:(uint16_t)numChannels
bitsPerSample:(uint16_t)bitsPerSample
error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END

51
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWtg.h

@ -0,0 +1,51 @@
//
// JLPcmToWtg.h
// JLAudioUnitKit
//
// Created by EzioChan on 2025/1/21.
// Copyright © 2025 ZhuHai JieLi Technology Co.,Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JLPcm2WtgModel : NSObject <NSCopying>
/// 音频文件
@property (nonatomic, copy) NSString *pcmPath;
/// 输出文件
@property (nonatomic, copy) NSString *wtgPath;
@end
/// JLPcmToWtgProtocol
@protocol JLPcmToWtgDelegate <NSObject>
/// 音频转码完成
/// - Parameter model: 音频转码模型
- (void)convertPcmToWtgDone:(JLPcm2WtgModel *)model;
@end
/// 音频转码
/// PCM 转 WTG
@interface JLPcmToWtg : NSObject
- (instancetype)init NS_UNAVAILABLE;
/// 音频转码初始化
/// - Parameter delegate: 音频转码代理
- (instancetype)initWithDelegate:(id<JLPcmToWtgDelegate>)delegate;
/// 音频转码
/// 当前接口模型只限制 PCM 文件格式为:8k 16bit
/// 输入的pcm文件(16bit,小端,8k采样率),wtg_filename:输出的wtg文件
/// - Parameter model: 音频转码模型
-(void)convertPcmToWtg:(JLPcm2WtgModel *)model;
@end
NS_ASSUME_NONNULL_END

54
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLSpeexDecoder.h

@ -0,0 +1,54 @@
//
// JLSpeexUnit.h
// JLAudioUnitKit
//
// Created by EzioChan on 2024/11/14.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class JLSpeexDecoder;
@protocol JLSpeexDelegate <NSObject>
/// Speex 数据解码
/// - Parameters:
/// - decoder: 解码器
/// - data: PCM 数据
/// - error: 错误信息
- (void)speexDecoder:(JLSpeexDecoder *)decoder Data:(NSData* _Nullable)data error:(NSError* _Nullable)error;
@end
typedef void(^JLSpeexConvertBlock)(NSString *_Nullable pcmPath,NSError *_Nullable error);
/// Speex 数据解码
@interface JLSpeexDecoder : NSObject
-(instancetype)init NS_UNAVAILABLE;
/// 初始化
/// - Parameter delegate: 委托
-(instancetype)initWithDelegate:(id<JLSpeexDelegate>)delegate;
/// 委托
@property (nonatomic, weak) id<JLSpeexDelegate> delegate;
/// 输入 Speex 数据
/// - Parameter data: Speex 数据
-(void)speexInputData:(NSData*)data;
/// 将 Speex 转换为 PCM
/// - Parameters:
/// - filePath: Speex 路径
/// - opPath: 输出路径
/// - result: 结果回调
-(void)speexConvertToPcm:(NSString *)filePath outPutFilePath:(NSString * _Nullable)opPath Result:(JLSpeexConvertBlock)result;
/// 释放对象
-(void)speexOnRelease;
@end
NS_ASSUME_NONNULL_END

BIN
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Info.plist

Binary file not shown.

BIN
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/JLAudioUnitKit

Binary file not shown.

6
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Modules/module.modulemap

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

52
local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/readme.txt

@ -0,0 +1,52 @@
DESCRIPTION
-----------
This package includes the files needed to build the fixed point implementation
of the ITU-T G.729 Appendix IV.
DIFFERENCES WITH G.729B
-----------------------
The ANSI-C source code simulating the bit-exact, fixed point simulation
software of enhanced VAD is integrated into the G.729B source code.
The modification to the G.729B source code updated files of coder.c,
cod_ld8k.c, bits.c, dec_sid.c, dtx.c, qsidgain.c, qsidlsf.c, tab_dtx.c,
tab_dtx.h, ld8k.h, deleted vad.c/h and integrated the enhanced VAD functions
into the G.729B project. The modification also replaced the original basic
operators used in the original G.729B source code by the basic operators of
ITU-T Software Tool Library STL2005.
COMPILATION
-----------
For UNIX systems the following makefiles are provided
coder.mak
decoder.mak
Edit the makefiles coder.mak and decoder.mak to set the proper options
for your system.
The command to compile and link all code on a UNIX system is
make -f coder.mak
make -f decoder.mak
For other platforms, the *.mak files can be used to work out the
compilation procedures.
USAGE
-----
The command line instruction for the encoder is as follows:
coder inputfile bitstreamfile dtx_option mode x
where:
inputfile : 8 kHz sampled data file containing 16 bit linear PCM signal
bitstreamfile: binary file containing bitstream
dtx_option : = 1 : DTX enabled 0 : DTX disabled
mode X : Run encoder with enhanced VAD in operating point X,
X=0 or 1 or 2 representing balanced or quality-preferred
or bandwidth saving operating point, respectively
The command line for the decoder is as follows:
decoder bitstreamfile outputfile
where:
bitstreamfile: binary file containing bitstream
outputfile : 8 kHz sampled data file containing 16 bit linear PCM signal

15
local_plugins/ble_service/ios/ble_service/Package.swift

@ -8,22 +8,29 @@ let package = Package(
.library(name: "ble-service", targets: ["ble_service"]) .library(name: "ble-service", targets: ["ble_service"])
], ],
dependencies: [ dependencies: [
// YbridOpus SPM 包(替代CocoaPods依赖)
.package(url: "https://github.com/ybrid/opus-swift.git", from: "0.8.0")
], ],
targets: [ targets: [
.target( .target(
name: "ble_service", name: "ble_service",
dependencies: [ dependencies: [
.product(name: "YbridOpus", package: "opus-swift") "opus",
"JLAudioUnitKit"
], ],
path: "Sources/ble_service", path: "Sources/ble_service",
linkerSettings: [ linkerSettings: [
.linkedFramework("AVFoundation"), .linkedFramework("AVFoundation"),
.linkedFramework("AudioToolbox"), .linkedFramework("AudioToolbox"),
.linkedFramework("CoreBluetooth") .linkedFramework("CoreBluetooth")
// 移除 AccessorySetupKit 链接,因为它只在 iOS 18+ 可用
] ]
),
// 本地opus框架作为二进制目标(使用xcframework格式)
.binaryTarget(
name: "opus", // 改为与 xcframework 内部模块名称一致
path: "opus.xcframework"
),
.binaryTarget(
name: "JLAudioUnitKit",
path: "JLAudioUnitKit.xcframework"
) )
] ]
) )

7
local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift

@ -20,7 +20,12 @@ class BleConst {
/** 通知特征UUID - 文档中定义为0000ABC2-0000-1111-2222-123456789ABC */ /** 通知特征UUID - 文档中定义为0000ABC2-0000-1111-2222-123456789ABC */
static let NOTIFY_CHAR_UUID = CBUUID(string: "0000abc2-0000-1111-2222-123456789abc") static let NOTIFY_CHAR_UUID = CBUUID(string: "0000abc2-0000-1111-2222-123456789abc")
/** 通话音频服务UUID - 文档中定义为0000ABC0-0001-1111-2222-123456789ABC */
static let CALL_AUDIO_SERVICE_UUID = CBUUID(string: "0000abc0-0001-1111-2222-123456789abc")
/** 通话写入音频特征UUID - 文档中定义为0000ABC1-0001-1111-2222-123456789ABC */
static let CALL_WRITE_AUDIO_CHAR_UUID = CBUUID(string: "0000abc1-0001-1111-2222-123456789abc")
/** 客户端特征配置描述符UUID */ /** 客户端特征配置描述符UUID */
static let CLIENT_CHAR_CONFIG_UUID = CBUUID(string: "00002902-0000-1000-8000-00805f9b34fb") static let CLIENT_CHAR_CONFIG_UUID = CBUUID(string: "00002902-0000-1000-8000-00805f9b34fb")

278
local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift

@ -28,6 +28,7 @@ public class BleService: NSObject {
// 数据相关回调 // 数据相关回调
func onAudioDataReceived(data: Data) func onAudioDataReceived(data: Data)
func onAudioDataReceived1(data: Data)
// 唤醒信号回调 // 唤醒信号回调
func onWakeupSignalReceived() func onWakeupSignalReceived()
@ -40,9 +41,14 @@ public class BleService: NSObject {
private var centralManager: CBCentralManager? private var centralManager: CBCentralManager?
private var peripheralDevice: CBPeripheral? private var peripheralDevice: CBPeripheral?
private var writeCharacteristic: CBCharacteristic? private var writeCharacteristic: CBCharacteristic?
private var callAudioCharacteristic: CBCharacteristic?
private var notifyCharacteristic: CBCharacteristic? private var notifyCharacteristic: CBCharacteristic?
private var audioCharacteristic: CBCharacteristic? private var audioCharacteristic: CBCharacteristic?
// 添加录制文件管理器
private var recordingFile: RecordingFile?
private var isScanning = false private var isScanning = false
private var scanTimer: Timer? private var scanTimer: Timer?
@ -80,6 +86,24 @@ public class BleService: NSObject {
// 是否回复指令码 // 是否回复指令码
private var cmdReplyType: UInt8 = 0 private var cmdReplyType: UInt8 = 0
// 新增:音频配置参数
private var sampleRate: Int32 = 16000
private var channels: Int32 = 1
private var frameSize: Int32 = 640
// MARK: - 音频数据发送相关属性
private var audioBuffer = [UInt8]() // 音频数据缓冲区
private var audioSendQueue = DispatchQueue(label: "com.yunqiinnovation.ble_service.audio_send", qos: .userInitiated)
private var audioDataQueue = [Data]() // 音频数据发送队列
private var isAudioSending = false // 音频发送状态
private var audioSendTimer: Timer? // 音频发送定时器
// 音频发送配置常量
private let AUDIO_CHUNK_SIZE = 120 // 每次发送120字节
private let AUDIO_SEND_INTERVAL: TimeInterval = 0.06 // 60毫秒间隔
private let audioQueueLock = NSLock() // 队列操作锁
//private var currentCommand: Command? //private var currentCommand: Command?
// 命令结构体 // 命令结构体
private struct Command { private struct Command {
@ -121,16 +145,29 @@ private var cmdReplyType: UInt8 = 0
} }
// 启动Opus解码流 // 启动Opus解码流
private func startOpusDecoding() { private func startOpusDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 640) {
// 检查参数是否与预设值匹配,如果不匹配则停止当前解码
if sampleRate != self.sampleRate || channels != self.channels || frameSize != self.frameSize {
opusProcessor?.stopDecoding()
}
let opusStarted = opusProcessor?.startDecoding( let opusStarted = opusProcessor?.startDecoding(
sampleRate: 16000, // 设备端Opus编码采样率 sampleRate: sampleRate, // 设备端Opus编码采样率
channels: 1, // 单声道 channels: channels, // 单声道
frameSize: 640 // 40ms at 16kHz = 640 samples frameSize: frameSize // 40ms at 16kHz = 640 samples
) ?? false ) ?? false
if !opusStarted { if !opusStarted {
os_log("Opus解码流启动失败", log: logger, type: .error) os_log("Opus解码流启动失败", log: logger, type: .error)
} }
else{
os_log("Opus解码流启动成功", log: logger, type: .info)
// 保存音频配置参数
self.sampleRate = sampleRate
self.channels = channels
self.frameSize = frameSize
}
} }
// MARK: - 回调管理 // MARK: - 回调管理
@ -345,17 +382,18 @@ private var cmdReplyType: UInt8 = 0
peripheralDevice?.delegate = self peripheralDevice?.delegate = self
updateConnectionState(BleConst.STATE_CONNECTED) updateConnectionState(BleConst.STATE_CONNECTED)
if opusProcessor?.isDecoding() != true { // if opusProcessor?.isDecoding() != true {
startOpusDecoding() startOpusDecoding()
} // }
// 清空特征引用,强制重新发现 // 清空特征引用,强制重新发现
writeCharacteristic = nil writeCharacteristic = nil
callAudioCharacteristic = nil
notifyCharacteristic = nil notifyCharacteristic = nil
audioCharacteristic = nil audioCharacteristic = nil
os_log("🔍 恢复会话时重新发现服务和特征", log: logger, type: .info) os_log("🔍 恢复会话时重新发现服务和特征", log: logger, type: .info)
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID, BleConst.CALL_AUDIO_SERVICE_UUID])
os_log("✅ 已恢复设备会话: %@", log: logger, type: .info, peripheral.identifier.uuidString) os_log("✅ 已恢复设备会话: %@", log: logger, type: .info, peripheral.identifier.uuidString)
} }
@ -369,6 +407,16 @@ private var cmdReplyType: UInt8 = 0
if centralManager != nil { if centralManager != nil {
isManualDisconnect = true // 标记为主动断开 isManualDisconnect = true // 标记为主动断开
// 停止音频发送定时器
stopAudioSendTimer()
// 清空音频缓冲区和队列
audioQueueLock.lock()
audioBuffer.removeAll()
audioDataQueue.removeAll()
audioQueueLock.unlock()
updateConnectionState(BleConst.STATE_DISCONNECTING) updateConnectionState(BleConst.STATE_DISCONNECTING)
centralManager?.cancelPeripheralConnection(peripheral) centralManager?.cancelPeripheralConnection(peripheral)
os_log("🔌 开始断开设备连接", log: logger, type: .info) os_log("🔌 开始断开设备连接", log: logger, type: .info)
@ -481,6 +529,12 @@ private var cmdReplyType: UInt8 = 0
callback.onAudioDataReceived(data: data) callback.onAudioDataReceived(data: data)
} }
} }
/// 通知所有回调接收到音频数据
private func notifyAudioDataReceived1(data: Data) {
for callback in callbacks {
callback.onAudioDataReceived1(data: data)
}
}
/// 通知所有回调接收到唤醒信号 /// 通知所有回调接收到唤醒信号
private func notifyWakeupSignalReceived() { private func notifyWakeupSignalReceived() {
@ -527,11 +581,23 @@ private var cmdReplyType: UInt8 = 0
} }
// MARK: - 编解码控制 // MARK: - 编解码控制
public func writeExternalAudioData(data: Data) {
print("pcm音频进入编码", data.count)
// 确保Opus编码器处于活跃状态(如果不是则启动)
// 将外部音频数据写入编码流,每次处理1280字节
opusProcessor?.encodePCMData(data)
}
/// 打开编码器 /// 打开编码器
func openEncoder() -> Bool { func openEncoder() -> Bool {
os_log("🎙️ 准备打开编码(设备侧)...", log: logger, type: .info) os_log("🎙️ 准备打开编码(设备侧)...", log: logger, type: .info)
// 确保Opus解码器处于活跃状态(如果不是则启动)
// if opusProcessor?.isDecoding() != true {
startOpusDecoding()
//}
let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT])
let result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) let result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData)
@ -549,25 +615,50 @@ private var cmdReplyType: UInt8 = 0
os_log("打开解码...", log: logger, type: .info) os_log("打开解码...", log: logger, type: .info)
// 确保Opus解码器处于活跃状态(如果不是则启动) // 确保Opus解码器处于活跃状态(如果不是则启动)
if opusProcessor?.isDecoding() != true { //if opusProcessor?.isDecoding() != true {
startOpusDecoding() startOpusDecoding()
} //}
// 发送命令通知设备开始解码 // 发送命令通知设备开始解码
let paramData = Data([BleConst.CODEC_CONTROL_DECODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) let paramData = Data([BleConst.CODEC_CONTROL_DECODE_ON, BleConst.AUDIO_CHANNEL_RIGHT])
return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData)
} }
/// 获取与Flutter getExternalStorageDirectories()一致的存储目录
/// 在iOS上,这相当于应用的Documents目录
func getExternalStorageDirectory() -> URL? {
// 获取Documents目录,这部分与Flutter的getExternalStorageDirectories()在iOS上的行为一致
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
}
/// 打开A2DP解码器 /// 打开A2DP解码器并开始录制
func openA2DPDecoder() -> Bool { func openA2DPDecoder() -> Bool {
os_log("打开A2DP解码...", log: logger, type: .info) // 确保Opus解码器处于活跃状态(如果不是则启动)
startOpusDecoding(sampleRate: 16000, channels: 2, frameSize: 1280)
opusProcessor?.startEncoding(sampleRate: 16000, channels: 1, frameRate: 20)
os_log("打开A2DP解码并开始录制...", log: logger, type: .info)
let paramData = Data([BleConst.CODEC_CONTROL_A2DP_PLAY, BleConst.AUDIO_CHANNEL_RIGHT]) let paramData = Data([BleConst.CODEC_CONTROL_A2DP_PLAY, BleConst.AUDIO_CHANNEL_RIGHT])
return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData)
} }
/// 关闭编解码器 /// 关闭编解码器并停止录制
func closeCodec() -> Bool { func closeCodec() -> Bool {
os_log("关闭编解码...", log: logger, type: .info) os_log("关闭编解码并停止录制...", log: logger, type: .info)
// 停止录制
recordingFile?.closeFile()
recordingFile = nil
// 停止音频发送定时器
stopAudioSendTimer()
// 清空音频缓冲区和队列
audioQueueLock.lock()
audioBuffer.removeAll()
audioDataQueue.removeAll()
audioQueueLock.unlock()
// 不停止Opus解码流,只发送命令通知设备关闭编解码 // 不停止Opus解码流,只发送命令通知设备关闭编解码
let paramData = Data([BleConst.CODEC_CONTROL_CLOSE, BleConst.AUDIO_CHANNEL_RIGHT]) let paramData = Data([BleConst.CODEC_CONTROL_CLOSE, BleConst.AUDIO_CHANNEL_RIGHT])
@ -1095,6 +1186,147 @@ extension BleService: SwiftOpusAudioProcessor.AudioDataCallback {
func onAudioDataReceived(data: Data) { func onAudioDataReceived(data: Data) {
notifyAudioDataReceived(data: data) notifyAudioDataReceived(data: data)
} }
/// 接收到解码后的PCM音频数据
func onAudioDataReceived1(data: Data) {
notifyAudioDataReceived1(data: data)
}
/// 接收到编码后的音频数据
/// - Parameter data: 编码后的音频数据
func onEncodedDataReceived(data: Data) {
// 保存编码数据到文件
recordingFile?.saveAudioData(data)
// 根据你的业务需求处理编码数据
// 例如:发送到蓝牙设备、保存到文件等
os_log("🎵 接收到编码音频数据: %d bytes", log: logger, type: .info, data.count)
// 修复条件绑定语句
guard let characteristic = callAudioCharacteristic,
let peripheral = peripheralDevice,
connectionState == BleConst.STATE_CONNECTED else {
os_log("通话音频特征未设置或设备未连接", log: logger, type: .error)
return
}
// 将编码音频数据加入到队列,按每次发送120字节,每次间隔60毫秒
addAudioDataToSendQueue(data: data)
// 启动音频发送定时器(如果尚未启动)
if !isAudioSending {
startAudioSendTimer()
}
}
/**
* 将音频数据添加到发送队列
* 只有当缓冲区达到120字节时才发送数据
* @param data 音频数据字节数组
*/
private func addAudioDataToSendQueue(data: Data) {
audioQueueLock.lock()
defer { audioQueueLock.unlock() }
do {
// 将新数据添加到缓冲区
audioBuffer.append(contentsOf: data)
// 当缓冲区达到120字节时,发送数据
while audioBuffer.count >= AUDIO_CHUNK_SIZE {
// 取出120字节数据
let chunk = Data(audioBuffer.prefix(AUDIO_CHUNK_SIZE))
audioBuffer.removeFirst(AUDIO_CHUNK_SIZE)
// 将数据加入发送队列
audioDataQueue.append(chunk)
}
// os_log("音频数据已加入缓冲区,当前缓冲区大小: %d 字节", log: logger, type: .debug, audioBuffer.count)
} catch {
os_log("处理音频数据异常: %{public}@", log: logger, type: .error, error.localizedDescription)
}
}
/**
* 启动音频数据发送定时器
*/
private func startAudioSendTimer() {
guard !isAudioSending else {
os_log("音频发送定时器已在运行", log: logger, type: .debug)
return
}
isAudioSending = true
// 在主队列中创建定时器
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.audioSendTimer = Timer.scheduledTimer(withTimeInterval: self.AUDIO_SEND_INTERVAL, repeats: true) { [weak self] _ in
self?.processAudioSendQueue()
}
os_log("音频发送定时器已启动", log: self.logger, type: .info)
}
}
/**
* 停止音频数据发送定时器
*/
private func stopAudioSendTimer() {
isAudioSending = false
DispatchQueue.main.async { [weak self] in
self?.audioSendTimer?.invalidate()
self?.audioSendTimer = nil
guard let self = self else { return }
os_log("音频发送定时器已停止", log: self.logger, type: .info)
}
}
/**
* 处理音频发送队列
*/
private func processAudioSendQueue() {
audioQueueLock.lock()
defer { audioQueueLock.unlock() }
guard !audioDataQueue.isEmpty else { return }
// 从队列中取出音频数据块
let audioChunk = audioDataQueue.removeFirst()
// 在后台队列中发送音频数据块
audioSendQueue.async { [weak self] in
self?.sendAudioChunk(audioChunk)
}
}
/**
* 发送音频数据块到蓝牙设备
* @param data 要发送的音频数据
*/
private func sendAudioChunk(_ data: Data) {
guard let characteristic = callAudioCharacteristic,
let peripheral = peripheralDevice,
connectionState == BleConst.STATE_CONNECTED else {
os_log("通话音频特征未设置或设备未连接,无法发送音频数据", log: logger, type: .error)
return
}
// 发送音频数据块
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
//判断是否发送成功
if peripheral.state == .connected {
os_log("🎵 发送音频数据块: %d bytes", log: logger, type: .debug, data.count)
} else {
os_log("🎵 发送音频数据块失败: %d bytes", log: logger, type: .debug, data.count)
}
}
} }
// MARK: - CBCentralManagerDelegate // MARK: - CBCentralManagerDelegate
@ -1392,12 +1624,12 @@ extension BleService: CBCentralManagerDelegate {
os_log("🔍 开始发现服务...", log: logger, type: .debug) 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, BleConst.CALL_AUDIO_SERVICE_UUID])
// 确保Opus解码器处于活跃状态 // 确保Opus解码器处于活跃状态
if opusProcessor?.isDecoding() != true { // if opusProcessor?.isDecoding() != true {
startOpusDecoding() startOpusDecoding()
} // }
} }
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
@ -1482,6 +1714,10 @@ extension BleService: CBPeripheralDelegate {
os_log("🔍 发现音频服务,开始发现音频特征...", log: logger, type: .debug) os_log("🔍 发现音频服务,开始发现音频特征...", log: logger, type: .debug)
// 音频服务,发现音频特征 // 音频服务,发现音频特征
peripheral.discoverCharacteristics([BleConst.RECEIVE_AUDIO_CHAR_UUID], for: service) peripheral.discoverCharacteristics([BleConst.RECEIVE_AUDIO_CHAR_UUID], for: service)
}else if service.uuid == BleConst.CALL_AUDIO_SERVICE_UUID {
os_log("🔍 发现通话音频服务,开始发现通话音频特征...", log: logger, type: .debug)
// 通话音频服务,发现通话音频特征
peripheral.discoverCharacteristics([BleConst.CALL_WRITE_AUDIO_CHAR_UUID], for: service)
} }
} }
} }
@ -1513,6 +1749,10 @@ extension BleService: CBPeripheralDelegate {
os_log("🎵 音频特征已设置,开始发现描述符...", log: logger, type: .info) os_log("🎵 音频特征已设置,开始发现描述符...", log: logger, type: .info)
// 先发现描述符,再配置通知 // 先发现描述符,再配置通知
peripheral.discoverDescriptors(for: characteristic) peripheral.discoverDescriptors(for: characteristic)
}else if characteristic.uuid == BleConst.CALL_WRITE_AUDIO_CHAR_UUID {
callAudioCharacteristic = characteristic
os_log("🎵 通话音频特征已设置,开始发现描述符...", log: logger, type: .info)
// 先发现描述符,再配置通知
} }
} }

135
local_plugins/ble_service/ios/ble_service/Sources/ble_service/RecordingFile.swift

@ -0,0 +1,135 @@
import Foundation
import os.log
/// 音频录制文件管理类
/// 负责创建音频文件并异步写入编码后的音频数据
@available(iOS 13.0, *)
class RecordingFile {
private var currentAudioFile: URL?
private var fileHandle: FileHandle?
// 用于异步写入的队列和线程
private let writeQueue = DispatchQueue(label: "com.yunqiinnovation.ble_service.recording_write", qos: .utility)
private var isWriting = false
private let writeLock = NSLock()
// 用于存储音频数据的缓冲区
private var totalBytesWritten = 0
var fileName = ""
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "RecordingFile")
/// 获取与Flutter getExternalStorageDirectories()一致的存储目录
/// 在iOS上,这相当于应用的Documents目录
private func getExternalStorageDirectory() -> URL? {
// 获取Documents目录,这部分与Flutter的getExternalStorageDirectories()在iOS上的行为一致
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
}
/// 创建新的音频文件
/// 如果文件已存在则直接返回
internal func createFile() {
writeLock.lock()
defer { writeLock.unlock() }
if fileHandle != nil || currentAudioFile != nil {
return
}
// 创建新的音频文件
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd_HHmmss"
let timestamp = dateFormatter.string(from: Date())
// 获取与Flutter一致的外部存储目录
guard let externalStorageDirectory = getExternalStorageDirectory() else {
os_log("无法获取外部存储目录", log: logger, type: .error)
return
}
// 使用与Android端相同的文件命名格式
let filePath = externalStorageDirectory.appendingPathComponent("\(fileName)_\(timestamp).opus")
currentAudioFile = filePath
do {
// 创建文件
FileManager.default.createFile(atPath: filePath.path, contents: nil, attributes: nil)
fileHandle = try FileHandle(forWritingTo: filePath)
isWriting = true
os_log("创建文件: %@", log: logger, type: .info, filePath.path)
} catch {
os_log("创建文件失败: %@", log: logger, type: .error, error.localizedDescription)
currentAudioFile = nil
fileHandle = nil
}
}
/// 异步保存音频数据到文件
/// - Parameter buffer: 编码后的音频数据
internal func saveAudioData(_ buffer: Data) {
guard isWriting, fileHandle != nil else {
return
}
// 异步写入数据
writeQueue.async { [weak self] in
guard let self = self else { return }
do {
try self.fileHandle?.write(contentsOf: buffer)
self.totalBytesWritten += buffer.count
os_log("写入音频数据: %d bytes, 总计: %d bytes", log: self.logger, type: .debug, buffer.count, self.totalBytesWritten)
} catch {
os_log("写入音频数据失败: %@", log: self.logger, type: .error, error.localizedDescription)
}
}
}
/// 关闭文件并完成录制
internal func closeFile() {
writeLock.lock()
defer { writeLock.unlock() }
guard let fileHandle = fileHandle, let currentFile = currentAudioFile else {
return
}
isWriting = false
// 等待写入队列完成
writeQueue.sync {
do {
try fileHandle.close()
// 检查文件大小
let fileSize = try FileManager.default.attributesOfItem(atPath: currentFile.path)[.size] as? Int64 ?? 0
if fileSize <= 0 {
// 删除空文件
try FileManager.default.removeItem(at: currentFile)
os_log("音频文件过小已删除: %@", log: logger, type: .info, currentFile.path)
} else {
os_log("音频文件保存完成: %@, 大小: %lld bytes", log: logger, type: .info, currentFile.path, fileSize)
}
} catch {
os_log("关闭文件失败: %@", log: logger, type: .error, error.localizedDescription)
}
}
self.fileHandle = nil
self.currentAudioFile = nil
self.totalBytesWritten = 0
}
/// 获取当前录制文件路径
/// - Returns: 当前录制文件的路径,如果没有正在录制则返回nil
internal func getCurrentFilePath() -> String? {
return currentAudioFile?.path
}
/// 检查是否正在录制
/// - Returns: 如果正在录制返回true,否则返回false
internal func isRecording() -> Bool {
return isWriting && fileHandle != nil
}
}

4
local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift

@ -236,6 +236,10 @@ extension SwiftBleServicePlugin: BleService.Callback {
} }
} }
public func onAudioDataReceived1(data: Data) {
}
public func onWakeupSignalReceived() { public func onWakeupSignalReceived() {
if let eventSink = statusEventSink { if let eventSink = statusEventSink {
DispatchQueue.main.async { DispatchQueue.main.async {

374
local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift

@ -1,22 +1,46 @@
import Foundation import Foundation
import AVFoundation import AVFoundation
import YbridOpus import opus
import JLAudioUnitKit
@available(iOS 13.0, *) @available(iOS 13.0, *)
class SwiftOpusAudioProcessor: NSObject { class SwiftOpusAudioProcessor: NSObject {
/// 音频数据回调协议
protocol AudioDataCallback: AnyObject { protocol AudioDataCallback: AnyObject {
func onAudioDataReceived(data: Data) func onAudioDataReceived(data: Data)
func onAudioDataReceived1(data: Data)
func onEncodedDataReceived(data: Data) // 编码数据回调
} }
// MARK: - 私有属性
// 状态标志
private var isDecodingActive = false private var isDecodingActive = false
private var isEncodingActive = false
private weak var callback: AudioDataCallback? private weak var callback: AudioDataCallback?
// Opus 解码器相关 - 使用 YbridOpus C API // Opus 解码器相关 - 使用 YbridOpus C API
private var decoder: OpaquePointer? private var decoder: OpaquePointer?
private var sampleRate: Int32 = 16000 private var sampleRate: Int32 = 16000
private var channels: Int32 = 1 private var channels: Int32 = 1
private let maxFrameSize: Int32 = 640 // 为16kHz优化:40ms * 16000Hz = 640 samples,与Android参数统一 private let maxFrameSize: Int32 = 640 // 为16kHz优化:40ms * 16000Hz = 640 samples
// MARK: - JLOpusEncoder 编码器相关属性
private var jlOpusEncoder: JLOpusEncoder?
private var encoderFormat: JLOpusFormat?
// MARK: - PCM数据队列相关属性
/// PCM数据缓存队列
private var pcmDataQueue = Data()
/// 队列访问锁,确保线程安全
private let queueLock = NSLock()
/// 每次处理的数据块大小(字节)
private let chunkSize = 640
/// 异步处理队列
private var processingQueue: DispatchQueue?
// MARK: - 初始化和清理
override init() { override init() {
super.init() super.init()
@ -26,6 +50,10 @@ class SwiftOpusAudioProcessor: NSObject {
cleanup() cleanup()
} }
// MARK: - 解码器管理
/// 线程安全的解码器初始化 /// 线程安全的解码器初始化
private func initializeOpusDecoder() -> Bool { private func initializeOpusDecoder() -> Bool {
objc_sync_enter(self) objc_sync_enter(self)
@ -58,12 +86,21 @@ class SwiftOpusAudioProcessor: NSObject {
} }
} }
// MARK: - 公共接口
/// 设置音频数据回调
/// - Parameter callback: 回调对象
func setCallback(_ callback: AudioDataCallback?) { func setCallback(_ callback: AudioDataCallback?) {
self.callback = callback self.callback = callback
} }
/// 启动Opus解码
/// - Parameters:
/// - sampleRate: 采样率
/// - channels: 声道数
/// - frameSize: 帧大小
/// - Returns: 是否启动成功
func startDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 40) -> Bool { func startDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 40) -> Bool {
// frameSize参数必须与Android版本的packetSize保持一致,确保双平台解码行为一致
if isDecodingActive { if isDecodingActive {
return true return true
} }
@ -76,10 +113,13 @@ class SwiftOpusAudioProcessor: NSObject {
} }
isDecodingActive = true isDecodingActive = true
print("Opus解码器启动成功 - 采样率: \(sampleRate)Hz, 声道: \(channels)")
return true return true
} }
/// 停止Opus解码
/// - Returns: 是否停止成功
func stopDecoding() -> Bool { func stopDecoding() -> Bool {
if !isDecodingActive { if !isDecodingActive {
return false return false
@ -87,10 +127,13 @@ class SwiftOpusAudioProcessor: NSObject {
isDecodingActive = false isDecodingActive = false
destroyOpusDecoder() destroyOpusDecoder()
print("Opus解码器已停止")
return true return true
} }
/// 处理音频数据进行解码
/// - Parameter data: Opus编码的音频数据
func processAudioData(_ data: Data) { func processAudioData(_ data: Data) {
guard isDecodingActive, let decoder = decoder, data.count > 0 else { guard isDecodingActive, let decoder = decoder, data.count > 0 else {
return return
@ -102,16 +145,13 @@ class SwiftOpusAudioProcessor: NSObject {
return return
} }
// 检查最小数据包大小(Opus最小包通常至少几个字节)
guard data.count >= 3 else { guard data.count >= 3 else {
print("音频数据包太小: \(data.count) bytes,跳过处理") print("音频数据包太小: \(data.count) bytes,跳过处理")
return return
} }
// 简单的 Opus 头部验证(检查是否是有效的 Opus 数据包格式) // 简单的 Opus 头部验证
let firstByte = data[0] let firstByte = data[0]
// Opus packets start with a TOC (Table of Contents) byte
// 检查TOC字节是否在合理范围内
if firstByte == 0x00 || firstByte == 0xFF { if firstByte == 0x00 || firstByte == 0xFF {
print("可疑的Opus数据包头部: 0x\(String(format: "%02X", firstByte)),跳过处理") print("可疑的Opus数据包头部: 0x\(String(format: "%02X", firstByte)),跳过处理")
return return
@ -123,12 +163,11 @@ class SwiftOpusAudioProcessor: NSObject {
} }
/// 改进的 Opus 解码方法,增强容错和线程安全 /// 改进的 Opus 解码方法,增强容错和线程安全
/// - Parameter packet: Opus数据包
private func decodeOpusPacket(_ packet: Data) { private func decodeOpusPacket(_ packet: Data) {
// 添加线程安全锁
objc_sync_enter(self) objc_sync_enter(self)
defer { objc_sync_exit(self) } defer { objc_sync_exit(self) }
// 验证解码器状态
guard let decoder = decoder, isDecodingActive else { guard let decoder = decoder, isDecodingActive else {
print("解码器未初始化或已停止") print("解码器未初始化或已停止")
return return
@ -138,16 +177,13 @@ class SwiftOpusAudioProcessor: NSObject {
var pcmBuffer = [opus_int16](repeating: 0, count: maxSamples * Int(channels)) var pcmBuffer = [opus_int16](repeating: 0, count: maxSamples * Int(channels))
let decodedSamples = packet.withUnsafeBytes { packetPtr in let decodedSamples = packet.withUnsafeBytes { packetPtr in
// 确保指针有效
guard let uint8Ptr = packetPtr.bindMemory(to: UInt8.self).baseAddress else { guard let uint8Ptr = packetPtr.bindMemory(to: UInt8.self).baseAddress else {
print("无法获取数据包指针") print("无法获取数据包指针")
return opus_int32(-1) return opus_int32(-1)
} }
// 添加异常捕获机制
var result: opus_int32 = -1 var result: opus_int32 = -1
// 使用 autoreleasepool 确保内存管理
autoreleasepool { autoreleasepool {
result = opus_decode( result = opus_decode(
decoder, decoder,
@ -162,12 +198,11 @@ class SwiftOpusAudioProcessor: NSObject {
return result return result
} }
// 增强的错误处理:检查各种错误情况 // 增强的错误处理
if decodedSamples <= 0 { if decodedSamples <= 0 {
let errorCode = decodedSamples let errorCode = decodedSamples
print("Opus解码失败,错误代码: \(errorCode),重新初始化解码器") print("Opus解码失败,错误代码: \(errorCode)")
// 根据错误类型进行不同的处理
if errorCode == -1 { // OPUS_BAD_ARG if errorCode == -1 { // OPUS_BAD_ARG
print("Opus解码参数错误,执行完全重置") print("Opus解码参数错误,执行完全重置")
performCompleteReset() performCompleteReset()
@ -184,18 +219,15 @@ class SwiftOpusAudioProcessor: NSObject {
return return
} }
// 验证解码结果的合理性
guard decodedSamples <= maxFrameSize else { guard decodedSamples <= maxFrameSize else {
print("Opus解码样本数超出预期: \(decodedSamples) > \(maxFrameSize)") print("Opus解码样本数超出预期: \(decodedSamples) > \(maxFrameSize)")
resetDecoder() resetDecoder()
return return
} }
// 提取解码的音频数据
let samplesPerChannel = Int(decodedSamples) let samplesPerChannel = Int(decodedSamples)
let totalSamples = samplesPerChannel * Int(channels) let totalSamples = samplesPerChannel * Int(channels)
// 验证总样本数的合理性
guard totalSamples <= pcmBuffer.count else { guard totalSamples <= pcmBuffer.count else {
print("计算的总样本数超出缓冲区: \(totalSamples) > \(pcmBuffer.count)") print("计算的总样本数超出缓冲区: \(totalSamples) > \(pcmBuffer.count)")
resetDecoder() resetDecoder()
@ -209,31 +241,28 @@ class SwiftOpusAudioProcessor: NSObject {
} }
} }
// 重置解码器的简单方法 /// 重置解码器的简单方法
private func resetDecoder() { private func resetDecoder() {
print("执行简单解码器重置") print("执行简单解码器重置")
destroyOpusDecoder() destroyOpusDecoder()
_ = initializeOpusDecoder() _ = initializeOpusDecoder()
} }
// 执行完全重置(包括停止和重启解码) /// 执行完全重置(包括停止和重启解码)
private func performCompleteReset() { private func performCompleteReset() {
print("执行完全解码器重置") print("执行完全解码器重置")
let wasActive = isDecodingActive let wasActive = isDecodingActive
let currentSampleRate = self.sampleRate let currentSampleRate = self.sampleRate
let currentChannels = self.channels let currentChannels = self.channels
// 完全停止解码
if wasActive { if wasActive {
isDecodingActive = false isDecodingActive = false
destroyOpusDecoder() destroyOpusDecoder()
} }
// 重新初始化所有状态
self.sampleRate = currentSampleRate self.sampleRate = currentSampleRate
self.channels = currentChannels self.channels = currentChannels
// 如果之前是活跃状态,重新启动
if wasActive { if wasActive {
guard initializeOpusDecoder() else { guard initializeOpusDecoder() else {
print("完全重置后重新初始化失败") print("完全重置后重新初始化失败")
@ -243,29 +272,312 @@ class SwiftOpusAudioProcessor: NSObject {
print("完全重置成功,解码器已重新启动") print("完全重置成功,解码器已重新启动")
} }
} }
/// 处理解码后的PCM音频数据
/// - Parameter pcmData: 解码后的PCM数据数组
private func processPCMData(_ pcmData: [opus_int16]) { private func processPCMData(_ pcmData: [opus_int16]) {
// 转换为 Data
let data = Data(bytes: pcmData, count: pcmData.count * MemoryLayout<opus_int16>.size) let data = Data(bytes: pcmData, count: pcmData.count * MemoryLayout<opus_int16>.size)
// 回调通知 guard !data.isEmpty else {
print("PCM数据为空")
return
}
print("Opus解码数据: \(data.count) bytes, 声道数: \(channels)")
if channels == 2 {
processStereoAudioData(data)
} else if channels == 1 {
DispatchQueue.main.async { [weak self] in
self?.callback?.onAudioDataReceived(data: data)
}
} else {
print("不支持的声道数: \(channels)")
}
}
/// 处理双声道音频数据,分离左右声道
/// - Parameter stereoData: 交错的双声道PCM数据
private func processStereoAudioData(_ stereoData: Data) {
let sampleCount = stereoData.count / 4 // 每个样本4字节(左右声道各2字节)
guard sampleCount > 0 else {
print("双声道数据样本数为0")
return
}
var leftBuffer = Data()
var rightBuffer = Data()
leftBuffer.reserveCapacity(sampleCount * 2)
rightBuffer.reserveCapacity(sampleCount * 2)
stereoData.withUnsafeBytes { bytes in
let uint8Ptr = bytes.bindMemory(to: UInt8.self)
for i in 0..<sampleCount {
let stereoIndex = i * 4
// 左声道(低位字节在前,高位字节在后)
leftBuffer.append(uint8Ptr[stereoIndex])
leftBuffer.append(uint8Ptr[stereoIndex + 1])
// 右声道
rightBuffer.append(uint8Ptr[stereoIndex + 2])
rightBuffer.append(uint8Ptr[stereoIndex + 3])
}
}
print("分离音频数据 - 左声道: \(leftBuffer.count) bytes, 右声道: \(rightBuffer.count) bytes")
// 使用SpeexKit编码右声道数据
//encodePCMData(rightBuffer)
// 回调分离后的音频数据
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.callback?.onAudioDataReceived(data: data) self?.callback?.onAudioDataReceived(data: rightBuffer)
self?.callback?.onAudioDataReceived1(data: leftBuffer)
} }
} }
// MARK: - 状态查询方法
/// 获取解码器状态
/// - Returns: 是否正在解码
func isDecoding() -> Bool { func isDecoding() -> Bool {
return isDecodingActive return isDecodingActive
} }
func cleanup() { /// 获取编码器状态
stopDecoding() /// - Returns: 是否正在编码
func isEncoding() -> Bool {
return isEncodingActive
} }
// MARK: - 公共接口
/// 获取当前配置信息 /// 获取当前配置信息
/// - Returns: 采样率和声道数
func getCurrentConfig() -> (sampleRate: Int32, channels: Int32) { func getCurrentConfig() -> (sampleRate: Int32, channels: Int32) {
return (sampleRate, channels) return (sampleRate, channels)
} }
/// 获取编码器配置信息
/// - Returns: 编码器配置元组
func getEncoderConfig() -> (sampleRate: Int32, channels: Int32) {
return (sampleRate, channels)
}
/// 启动JLOpusEncoder编码(完善版本)
/// - Parameters:
/// - sampleRate: 采样率(默认16000Hz)
/// - channels: 声道数(默认1)
/// - frameRate: 帧长度(默认20ms)
/// - isHeader: 是否包含头部信息
/// - Returns: 是否启动成功
func startEncoding(sampleRate: Int32 = 16000,
channels: Int32 = 1,
frameRate: Int32 = 20,
isHeader: Bool = false) -> Bool {
if isEncodingActive {
return true
}
print("启动JLOpusEncoder编码 - 采样率: \(sampleRate)Hz, 声道: \(channels), 帧长度: \(frameRate)ms, 头部: \(isHeader)")
// 创建JLOpusFormat配置
encoderFormat = JLOpusFormat.defaultFormats()
encoderFormat?.sampleRate = Int32(sampleRate)
encoderFormat?.channels = Int32(channels)
encoderFormat?.frameDuration = Int32(frameRate)
encoderFormat?.hasDataHeader = isHeader
// 根据配置计算数据大小
if channels == 1 {
encoderFormat?.dataSize = frameRate == 20 ? 40 : 80 // 20ms=40bytes, 40ms=80bytes
} else {
encoderFormat?.dataSize = frameRate == 20 ? 80 : 160 // 双声道翻倍
}
guard let format = encoderFormat else {
print("编码器格式配置失败")
return false
}
// 初始化JLOpusEncoder
jlOpusEncoder = JLOpusEncoder(format: format, delegate: self)
guard jlOpusEncoder != nil else {
print("JLOpusEncoder初始化失败")
return false
}
isEncodingActive = true
print("JLOpusEncoder启动成功")
return true
}
/// 停止JLOpusEncoder编码(完善版本)
/// - Returns: 是否停止成功
func stopEncoding() -> Bool {
if !isEncodingActive {
return false
}
isEncodingActive = false
// 清空PCM数据队列
clearPCMQueue()
// 释放JLOpusEncoder资源
jlOpusEncoder?.opusOnRelease()
jlOpusEncoder = nil
encoderFormat = nil
processingQueue = nil
print("JLOpusEncoder已停止")
return true
}
/// 编码PCM音频数据
/// - Parameter pcmData: PCM音频数据
func encodePCMData(_ pcmData: Data) {
guard isEncodingActive, let encoder = jlOpusEncoder else {
print("编码器未启动或不可用")
return
}
guard !pcmData.isEmpty else {
print("PCM数据为空")
return
}
print("接收PCM数据 - 数据大小: \(pcmData.count) bytes")
// 将新数据加入队列
queueLock.lock()
pcmDataQueue.append(pcmData)
let currentQueueSize = pcmDataQueue.count
queueLock.unlock()
print("PCM数据已加入队列 - 当前队列大小: \(currentQueueSize) bytes")
// 异步处理队列中的数据
processQueuedData()
}
/// 处理队列中的PCM数据
private func processQueuedData() {
guard isEncodingActive, let encoder = jlOpusEncoder else {
return
}
// 使用专用队列进行异步处理
if processingQueue == nil {
processingQueue = DispatchQueue(label: "com.opus.processing", qos: .userInitiated)
}
processingQueue?.async { [weak self, weak encoder] in
guard let self = self, let encoder = encoder else {
print("编码器或处理器已释放")
return
}
while self.isEncodingActive {
var chunkData: Data?
// 从队列中取出数据
self.queueLock.lock()
if self.pcmDataQueue.count >= self.chunkSize {
// 取出640字节数据
chunkData = self.pcmDataQueue.subdata(in: 0..<self.chunkSize)
self.pcmDataQueue.removeSubrange(0..<self.chunkSize)
} else if self.pcmDataQueue.count > 0 {
// 不足640字节,补齐静音数据
let availableBytes = self.pcmDataQueue.count
let silentBytesNeeded = self.chunkSize - availableBytes
// 创建静音数据(16位PCM静音为0)
let silentData = Data(repeating: 0, count: silentBytesNeeded)
// 组合实际数据和静音数据
chunkData = Data()
chunkData?.append(self.pcmDataQueue)
chunkData?.append(silentData)
// 清空队列
self.pcmDataQueue.removeAll()
print("数据不足640字节,已补齐静音数据 - 实际数据: \(availableBytes) bytes, 静音数据: \(silentBytesNeeded) bytes")
}
self.queueLock.unlock()
// 如果有数据需要编码
if let dataToEncode = chunkData {
guard self.isEncodingActive else {
print("编码已停止,退出处理循环")
break
}
// 编码数据块
encoder.opusEncode(dataToEncode)
print("已编码音频块 - 数据大小: \(dataToEncode.count) bytes")
} else {
// 队列为空,退出循环
break
}
}
print("队列处理完成")
}
}
/// 清空PCM数据队列
private func clearPCMQueue() {
queueLock.lock()
pcmDataQueue.removeAll()
queueLock.unlock()
print("PCM数据队列已清空")
}
/// 清理所有资源
func cleanup() {
stopDecoding()
stopEncoding()
}
}
// MARK: - JLOpusEncoderDelegate
@available(iOS 13.0, *)
extension SwiftOpusAudioProcessor: JLOpusEncoderDelegate {
/// JLOpusEncoder编码回调
/// - Parameters:
/// - encoder: 编码器实例
/// - data: 编码后的Opus数据
/// - error: 错误信息
func opusEncoder(_ encoder: JLOpusEncoder, data: Data?, error: (any Error)?) {
if let error = error {
print("Opus编码错误: \(error.localizedDescription)")
return
}
guard let opusData = data, !opusData.isEmpty else {
print("编码后的Opus数据为空")
return
}
print("Opus编码成功 - 数据大小: \(opusData.count) bytes")
// 回调编码后的数据到主线程
DispatchQueue.main.async { [weak self] in
self?.callback?.onEncodedDataReceived(data: opusData)
}
}
} }

85
local_plugins/ble_service/ios/ble_service/Sources/ble_service/opuse.swift

@ -0,0 +1,85 @@
import Foundation
import opus
class OpusAudioProcessor {
private var encoder: OpaquePointer?
private var decoder: OpaquePointer?
/**
* 初始化Opus编码器
* @param sampleRate 采样率 (8000, 12000, 16000, 24000, 48000)
* @param channels 声道数 (1 = 单声道, 2 = 立体声)
* @param application 应用类型 (OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_RESTRICTED_LOWDELAY)
*/
func initializeEncoder(sampleRate: Int32, channels: Int32, application: Int32) -> Bool {
var error: Int32 = 0
encoder = opus_encoder_create(sampleRate, channels, application, &error)
return error == OPUS_OK
}
/**
* 初始化Opus解码器
* @param sampleRate 采样率
* @param channels 声道数
*/
func initializeDecoder(sampleRate: Int32, channels: Int32) -> Bool {
var error: Int32 = 0
decoder = opus_decoder_create(sampleRate, channels, &error)
return error == OPUS_OK
}
/**
* 编码音频数据
* @param pcmData PCM音频数据
* @param frameSize 帧大小
* @param maxDataBytes 最大输出字节数
* @return 编码后的数据
*/
func encode(pcmData: [Int16], frameSize: Int32, maxDataBytes: Int32) -> Data? {
guard let encoder = encoder else { return nil }
let outputBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(maxDataBytes))
defer { outputBuffer.deallocate() }
let encodedBytes = opus_encode(encoder, pcmData, frameSize, outputBuffer, maxDataBytes)
if encodedBytes > 0 {
return Data(bytes: outputBuffer, count: Int(encodedBytes))
}
return nil
}
/**
* 解码音频数据
* @param encodedData 编码后的数据
* @param frameSize 帧大小
* @return 解码后的PCM数据
*/
func decode(encodedData: Data, frameSize: Int32) -> [Int16]? {
guard let decoder = decoder else { return nil }
let outputBuffer = UnsafeMutablePointer<Int16>.allocate(capacity: Int(frameSize * 2)) // 假设最多2声道
defer { outputBuffer.deallocate() }
let decodedSamples = encodedData.withUnsafeBytes { bytes in
opus_decode(decoder, bytes.bindMemory(to: UInt8.self).baseAddress, Int32(encodedData.count), outputBuffer, frameSize, 0)
}
if decodedSamples > 0 {
return Array(UnsafeBufferPointer(start: outputBuffer, count: Int(decodedSamples * 2)))
}
return nil
}
/**
* 清理资源
*/
deinit {
if let encoder = encoder {
opus_encoder_destroy(encoder)
}
if let decoder = decoder {
opus_decoder_destroy(decoder)
}
}
}

43
local_plugins/ble_service/ios/ble_service/opus.xcframework/Info.plist

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>opus.framework/opus</string>
<key>LibraryIdentifier</key>
<string>ios-x86_64-simulator</string>
<key>LibraryPath</key>
<string>opus.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>opus.framework/opus</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>opus.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

981
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus.h

@ -0,0 +1,981 @@
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
Written by Jean-Marc Valin and Koen Vos */
/*
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.
*/
/**
* @file opus.h
* @brief Opus reference implementation API
*/
#ifndef OPUS_H
#define OPUS_H
#include "opus_types.h"
#include "opus_defines.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @mainpage Opus
*
* The Opus codec is designed for interactive speech and audio transmission over the Internet.
* It is designed by the IETF Codec Working Group and incorporates technology from
* Skype's SILK codec and Xiph.Org's CELT codec.
*
* The Opus codec is designed to 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. Its main features are:
* @li Sampling rates from 8 to 48 kHz
* @li Bit-rates from 6 kb/s to 510 kb/s
* @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR)
* @li Audio bandwidth from narrowband to full-band
* @li Support for speech and music
* @li Support for mono and stereo
* @li Support for multichannel (up to 255 channels)
* @li Frame sizes from 2.5 ms to 60 ms
* @li Good loss robustness and packet loss concealment (PLC)
* @li Floating point and fixed-point implementation
*
* Documentation sections:
* @li @ref opus_encoder
* @li @ref opus_decoder
* @li @ref opus_repacketizer
* @li @ref opus_multistream
* @li @ref opus_libinfo
* @li @ref opus_custom
*/
/** @defgroup opus_encoder Opus Encoder
* @{
*
* @brief This page describes the process and functions used to encode Opus.
*
* Since Opus is a stateful codec, the encoding process starts with creating an encoder
* state. This can be done with:
*
* @code
* int error;
* OpusEncoder *enc;
* enc = opus_encoder_create(Fs, channels, application, &error);
* @endcode
*
* From this point, @c enc can be used for encoding an audio stream. An encoder state
* @b must @b not be used for more than one stream at the same time. Similarly, the encoder
* state @b must @b not be re-initialized for each frame.
*
* While opus_encoder_create() allocates memory for the state, it's also possible
* to initialize pre-allocated memory:
*
* @code
* int size;
* int error;
* OpusEncoder *enc;
* size = opus_encoder_get_size(channels);
* enc = malloc(size);
* error = opus_encoder_init(enc, Fs, channels, application);
* @endcode
*
* where opus_encoder_get_size() returns the required size for the encoder state. Note that
* future versions of this code may change the size, so no assuptions should be made about it.
*
* The encoder state is always continuous in memory and only a shallow copy is sufficient
* to copy it (e.g. memcpy())
*
* It is possible to change some of the encoder's settings using the opus_encoder_ctl()
* interface. All these settings already default to the recommended value, so they should
* only be changed when necessary. The most common settings one may want to change are:
*
* @code
* opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
* opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
* opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type));
* @endcode
*
* where
*
* @arg bitrate is in bits per second (b/s)
* @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest
* @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC
*
* See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream.
*
* To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data:
* @code
* len = opus_encode(enc, audio_frame, frame_size, packet, max_packet);
* @endcode
*
* where
* <ul>
* <li>audio_frame is the audio data in opus_int16 (or float for opus_encode_float())</li>
* <li>frame_size is the duration of the frame in samples (per channel)</li>
* <li>packet is the byte array to which the compressed data is written</li>
* <li>max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended).
* Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.</li>
* </ul>
*
* opus_encode() and opus_encode_float() return the number of bytes actually written to the packet.
* The return value <b>can be negative</b>, which indicates that an error has occurred. If the return value
* is 2 bytes or less, then the packet does not need to be transmitted (DTX).
*
* Once the encoder state if no longer needed, it can be destroyed with
*
* @code
* opus_encoder_destroy(enc);
* @endcode
*
* If the encoder was created with opus_encoder_init() rather than opus_encoder_create(),
* then no action is required aside from potentially freeing the memory that was manually
* allocated for it (calling free(enc) for the example above)
*
*/
/** Opus encoder state.
* This contains the complete state of an Opus encoder.
* It is position independent and can be freely copied.
* @see opus_encoder_create,opus_encoder_init
*/
typedef struct OpusEncoder OpusEncoder;
/** Gets the size of an <code>OpusEncoder</code> structure.
* @param[in] channels <tt>int</tt>: Number of channels.
* This must be 1 or 2.
* @returns The size in bytes.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels);
/**
*/
/** Allocates and initializes an encoder state.
* There are three coding modes:
*
* @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice
* signals. It enhances the input signal by high-pass filtering and
* emphasizing formants and harmonics. Optionally it includes in-band
* forward error correction to protect against packet loss. Use this
* mode for typical VoIP applications. Because of the enhancement,
* even at high bitrates the output may sound different from the input.
*
* @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most
* non-voice signals like music. Use this mode for music and mixed
* (music/voice) content, broadcast, and applications requiring less
* than 15 ms of coding delay.
*
* @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that
* disables the speech-optimized mode in exchange for slightly reduced delay.
* This mode can only be set on an newly initialized or freshly reset encoder
* because it changes the codec delay.
*
* This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution).
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
* @param [in] application <tt>int</tt>: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY)
* @param [out] error <tt>int*</tt>: @ref opus_errorcodes
* @note Regardless of the sampling rate and number channels selected, the Opus encoder
* can switch to a lower audio bandwidth or number of channels if the bitrate
* selected is too low. This also means that it is safe to always use 48 kHz stereo input
* and let the encoder optimize the encoding.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create(
opus_int32 Fs,
int channels,
int application,
int *error
);
/** Initializes a previously allocated encoder state
* The memory pointed to by st must be at least the size returned by opus_encoder_get_size().
* This is intended for applications which use their own allocator instead of malloc.
* @see opus_encoder_create(),opus_encoder_get_size()
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
* @param [in] application <tt>int</tt>: Coding mode (OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO/OPUS_APPLICATION_RESTRICTED_LOWDELAY)
* @retval #OPUS_OK Success or @ref opus_errorcodes
*/
OPUS_EXPORT int opus_encoder_init(
OpusEncoder *st,
opus_int32 Fs,
int channels,
int application
) OPUS_ARG_NONNULL(1);
/** Encodes an Opus frame.
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
* @param [in] pcm <tt>opus_int16*</tt>: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16)
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
* input signal.
* This must be an Opus frame size for
* the encoder's sampling rate.
* For example, at 48 kHz the permitted
* values are 120, 240, 480, 960, 1920,
* and 2880.
* Passing in a duration of less than
* 10 ms (480 samples at 48 kHz) will
* prevent the encoder from using the LPC
* or hybrid modes.
* @param [out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode(
OpusEncoder *st,
const opus_int16 *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Encodes an Opus frame from floating point input.
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
* @param [in] pcm <tt>float*</tt>: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0.
* Samples with a range beyond +/-1.0 are supported but will
* be clipped by decoders using the integer API and should
* only be used if it is known that the far end supports
* extended dynamic range.
* length is frame_size*channels*sizeof(float)
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
* input signal.
* This must be an Opus frame size for
* the encoder's sampling rate.
* For example, at 48 kHz the permitted
* values are 120, 240, 480, 960, 1920,
* and 2880.
* Passing in a duration of less than
* 10 ms (480 samples at 48 kHz) will
* prevent the encoder from using the LPC
* or hybrid modes.
* @param [out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float(
OpusEncoder *st,
const float *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Frees an <code>OpusEncoder</code> allocated by opus_encoder_create().
* @param[in] st <tt>OpusEncoder*</tt>: State to be freed.
*/
OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st);
/** Perform a CTL function on an Opus encoder.
*
* Generally the request and subsequent arguments are generated
* by a convenience macro.
* @param st <tt>OpusEncoder*</tt>: Encoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls or
* @ref opus_encoderctls.
* @see opus_genericctls
* @see opus_encoderctls
*/
OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/**@}*/
/** @defgroup opus_decoder Opus Decoder
* @{
*
* @brief This page describes the process and functions used to decode Opus.
*
* The decoding process also starts with creating a decoder
* state. This can be done with:
* @code
* int error;
* OpusDecoder *dec;
* dec = opus_decoder_create(Fs, channels, &error);
* @endcode
* where
* @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000
* @li channels is the number of channels (1 or 2)
* @li error will hold the error code in case of failure (or #OPUS_OK on success)
* @li the return value is a newly created decoder state to be used for decoding
*
* While opus_decoder_create() allocates memory for the state, it's also possible
* to initialize pre-allocated memory:
* @code
* int size;
* int error;
* OpusDecoder *dec;
* size = opus_decoder_get_size(channels);
* dec = malloc(size);
* error = opus_decoder_init(dec, Fs, channels);
* @endcode
* where opus_decoder_get_size() returns the required size for the decoder state. Note that
* future versions of this code may change the size, so no assuptions should be made about it.
*
* The decoder state is always continuous in memory and only a shallow copy is sufficient
* to copy it (e.g. memcpy())
*
* To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data:
* @code
* frame_size = opus_decode(dec, packet, len, decoded, max_size, 0);
* @endcode
* where
*
* @li packet is the byte array containing the compressed data
* @li len is the exact number of bytes contained in the packet
* @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float())
* @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array
*
* opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet.
* If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio
* buffer is too small to hold the decoded audio.
*
* Opus is a stateful codec with overlapping blocks and as a result Opus
* packets are not coded independently of each other. Packets must be
* passed into the decoder serially and in the correct order for a correct
* decode. Lost packets can be replaced with loss concealment by calling
* the decoder with a null pointer and zero length for the missing packet.
*
* A single codec state may only be accessed from a single thread at
* a time and any required locking must be performed by the caller. Separate
* streams must be decoded with separate decoder states and can be decoded
* in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK
* defined.
*
*/
/** Opus decoder state.
* This contains the complete state of an Opus decoder.
* It is position independent and can be freely copied.
* @see opus_decoder_create,opus_decoder_init
*/
typedef struct OpusDecoder OpusDecoder;
/** Gets the size of an <code>OpusDecoder</code> structure.
* @param [in] channels <tt>int</tt>: Number of channels.
* This must be 1 or 2.
* @returns The size in bytes.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels);
/** Allocates and initializes a decoder state.
* @param [in] Fs <tt>opus_int32</tt>: Sample rate to decode at (Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
* @param [out] error <tt>int*</tt>: #OPUS_OK Success or @ref opus_errorcodes
*
* Internally Opus stores data at 48000 Hz, so that should be the default
* value for Fs. However, the decoder can efficiently decode to buffers
* at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use
* data at the full sample rate, or knows the compressed data doesn't
* use the full frequency range, it can request decoding at a reduced
* rate. Likewise, the decoder is capable of filling in either mono or
* interleaved stereo pcm buffers, at the caller's request.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create(
opus_int32 Fs,
int channels,
int *error
);
/** Initializes a previously allocated decoder state.
* The state must be at least the size returned by opus_decoder_get_size().
* This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state.
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate to decode to (Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
* @retval #OPUS_OK Success or @ref opus_errorcodes
*/
OPUS_EXPORT int opus_decoder_init(
OpusDecoder *st,
opus_int32 Fs,
int channels
) OPUS_ARG_NONNULL(1);
/** Decode an Opus packet.
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload*
* @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
* is frame_size*channels*sizeof(opus_int16)
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
* decoded. If no such data is available, the frame is decoded as if it were lost.
* @returns Number of decoded samples or @ref opus_errorcodes
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode(
OpusDecoder *st,
const unsigned char *data,
opus_int32 len,
opus_int16 *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Decode an Opus packet with floating point output.
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload
* @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
* is frame_size*channels*sizeof(float)
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
* decoded. If no such data is available the frame is decoded as if it were lost.
* @returns Number of decoded samples or @ref opus_errorcodes
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float(
OpusDecoder *st,
const unsigned char *data,
opus_int32 len,
float *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Perform a CTL function on an Opus decoder.
*
* Generally the request and subsequent arguments are generated
* by a convenience macro.
* @param st <tt>OpusDecoder*</tt>: Decoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls or
* @ref opus_decoderctls.
* @see opus_genericctls
* @see opus_decoderctls
*/
OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/** Frees an <code>OpusDecoder</code> allocated by opus_decoder_create().
* @param[in] st <tt>OpusDecoder*</tt>: State to be freed.
*/
OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);
/** Parse an opus packet into one or more frames.
* Opus_decode will perform this operation internally so most applications do
* not need to use this function.
* This function does not copy the frames, the returned pointers are pointers into
* the input packet.
* @param [in] data <tt>char*</tt>: Opus packet to be parsed
* @param [in] len <tt>opus_int32</tt>: size of data
* @param [out] out_toc <tt>char*</tt>: TOC pointer
* @param [out] frames <tt>char*[48]</tt> encapsulated frames
* @param [out] size <tt>opus_int16[48]</tt> sizes of the encapsulated frames
* @param [out] payload_offset <tt>int*</tt>: returns the position of the payload within the packet (in bytes)
* @returns number of frames
*/
OPUS_EXPORT int opus_packet_parse(
const unsigned char *data,
opus_int32 len,
unsigned char *out_toc,
const unsigned char *frames[48],
opus_int16 size[48],
int *payload_offset
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Gets the bandwidth of an Opus packet.
* @param [in] data <tt>char*</tt>: Opus packet
* @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass)
* @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass)
* @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass)
* @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass)
* @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass)
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1);
/** Gets the number of samples per frame from an Opus packet.
* @param [in] data <tt>char*</tt>: Opus packet.
* This must contain at least one byte of
* data.
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
* This must be a multiple of 400, or
* inaccurate results will be returned.
* @returns Number of samples per frame.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1);
/** Gets the number of channels from an Opus packet.
* @param [in] data <tt>char*</tt>: Opus packet
* @returns Number of channels
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1);
/** Gets the number of frames in an Opus packet.
* @param [in] packet <tt>char*</tt>: Opus packet
* @param [in] len <tt>opus_int32</tt>: Length of packet
* @returns Number of frames
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1);
/** Gets the number of samples of an Opus packet.
* @param [in] packet <tt>char*</tt>: Opus packet
* @param [in] len <tt>opus_int32</tt>: Length of packet
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
* This must be a multiple of 400, or
* inaccurate results will be returned.
* @returns Number of samples
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1);
/** Gets the number of samples of an Opus packet.
* @param [in] dec <tt>OpusDecoder*</tt>: Decoder state
* @param [in] packet <tt>char*</tt>: Opus packet
* @param [in] len <tt>opus_int32</tt>: Length of packet
* @returns Number of samples
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
/** Applies soft-clipping to bring a float signal within the [-1,1] range. If
* the signal is already in that range, nothing is done. If there are values
* outside of [-1,1], then the signal is clipped as smoothly as possible to
* both fit in the range and avoid creating excessive distortion in the
* process.
* @param [in,out] pcm <tt>float*</tt>: Input PCM and modified PCM
* @param [in] frame_size <tt>int</tt> Number of samples per channel to process
* @param [in] channels <tt>int</tt>: Number of channels
* @param [in,out] softclip_mem <tt>float*</tt>: State memory for the soft clipping process (one float per channel, initialized to zero)
*/
OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem);
/**@}*/
/** @defgroup opus_repacketizer Repacketizer
* @{
*
* The repacketizer can be used to merge multiple Opus packets into a single
* packet or alternatively to split Opus packets that have previously been
* merged. Splitting valid Opus packets is always guaranteed to succeed,
* whereas merging valid packets only succeeds if all frames have the same
* mode, bandwidth, and frame size, and when the total duration of the merged
* packet is no more than 120 ms. The 120 ms limit comes from the
* specification and limits decoder memory requirements at a point where
* framing overhead becomes negligible.
*
* The repacketizer currently only operates on elementary Opus
* streams. It will not manipualte multistream packets successfully, except in
* the degenerate case where they consist of data from a single stream.
*
* The repacketizing process starts with creating a repacketizer state, either
* by calling opus_repacketizer_create() or by allocating the memory yourself,
* e.g.,
* @code
* OpusRepacketizer *rp;
* rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size());
* if (rp != NULL)
* opus_repacketizer_init(rp);
* @endcode
*
* Then the application should submit packets with opus_repacketizer_cat(),
* extract new packets with opus_repacketizer_out() or
* opus_repacketizer_out_range(), and then reset the state for the next set of
* input packets via opus_repacketizer_init().
*
* For example, to split a sequence of packets into individual frames:
* @code
* unsigned char *data;
* int len;
* while (get_next_packet(&data, &len))
* {
* unsigned char out[1276];
* opus_int32 out_len;
* int nb_frames;
* int err;
* int i;
* err = opus_repacketizer_cat(rp, data, len);
* if (err != OPUS_OK)
* {
* release_packet(data);
* return err;
* }
* nb_frames = opus_repacketizer_get_nb_frames(rp);
* for (i = 0; i < nb_frames; i++)
* {
* out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out));
* if (out_len < 0)
* {
* release_packet(data);
* return (int)out_len;
* }
* output_next_packet(out, out_len);
* }
* opus_repacketizer_init(rp);
* release_packet(data);
* }
* @endcode
*
* Alternatively, to combine a sequence of frames into packets that each
* contain up to <code>TARGET_DURATION_MS</code> milliseconds of data:
* @code
* // The maximum number of packets with duration TARGET_DURATION_MS occurs
* // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5)
* // packets.
* unsigned char *data[(TARGET_DURATION_MS*2/5)+1];
* opus_int32 len[(TARGET_DURATION_MS*2/5)+1];
* int nb_packets;
* unsigned char out[1277*(TARGET_DURATION_MS*2/2)];
* opus_int32 out_len;
* int prev_toc;
* nb_packets = 0;
* while (get_next_packet(data+nb_packets, len+nb_packets))
* {
* int nb_frames;
* int err;
* nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]);
* if (nb_frames < 1)
* {
* release_packets(data, nb_packets+1);
* return nb_frames;
* }
* nb_frames += opus_repacketizer_get_nb_frames(rp);
* // If adding the next packet would exceed our target, or it has an
* // incompatible TOC sequence, output the packets we already have before
* // submitting it.
* // N.B., The nb_packets > 0 check ensures we've submitted at least one
* // packet since the last call to opus_repacketizer_init(). Otherwise a
* // single packet longer than TARGET_DURATION_MS would cause us to try to
* // output an (invalid) empty packet. It also ensures that prev_toc has
* // been set to a valid value. Additionally, len[nb_packets] > 0 is
* // guaranteed by the call to opus_packet_get_nb_frames() above, so the
* // reference to data[nb_packets][0] should be valid.
* if (nb_packets > 0 && (
* ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) ||
* opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames >
* TARGET_DURATION_MS*48))
* {
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
* if (out_len < 0)
* {
* release_packets(data, nb_packets+1);
* return (int)out_len;
* }
* output_next_packet(out, out_len);
* opus_repacketizer_init(rp);
* release_packets(data, nb_packets);
* data[0] = data[nb_packets];
* len[0] = len[nb_packets];
* nb_packets = 0;
* }
* err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]);
* if (err != OPUS_OK)
* {
* release_packets(data, nb_packets+1);
* return err;
* }
* prev_toc = data[nb_packets][0];
* nb_packets++;
* }
* // Output the final, partial packet.
* if (nb_packets > 0)
* {
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
* release_packets(data, nb_packets);
* if (out_len < 0)
* return (int)out_len;
* output_next_packet(out, out_len);
* }
* @endcode
*
* An alternate way of merging packets is to simply call opus_repacketizer_cat()
* unconditionally until it fails. At that point, the merged packet can be
* obtained with opus_repacketizer_out() and the input packet for which
* opus_repacketizer_cat() needs to be re-added to a newly reinitialized
* repacketizer state.
*/
typedef struct OpusRepacketizer OpusRepacketizer;
/** Gets the size of an <code>OpusRepacketizer</code> structure.
* @returns The size in bytes.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void);
/** (Re)initializes a previously allocated repacketizer state.
* The state must be at least the size returned by opus_repacketizer_get_size().
* This can be used for applications which use their own allocator instead of
* malloc().
* It must also be called to reset the queue of packets waiting to be
* repacketized, which is necessary if the maximum packet duration of 120 ms
* is reached or if you wish to submit packets with a different Opus
* configuration (coding mode, audio bandwidth, frame size, or channel count).
* Failure to do so will prevent a new packet from being added with
* opus_repacketizer_cat().
* @see opus_repacketizer_create
* @see opus_repacketizer_get_size
* @see opus_repacketizer_cat
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to
* (re)initialize.
* @returns A pointer to the same repacketizer state that was passed in.
*/
OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
/** Allocates memory and initializes the new repacketizer with
* opus_repacketizer_init().
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void);
/** Frees an <code>OpusRepacketizer</code> allocated by
* opus_repacketizer_create().
* @param[in] rp <tt>OpusRepacketizer*</tt>: State to be freed.
*/
OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp);
/** Add a packet to the current repacketizer state.
* This packet must match the configuration of any packets already submitted
* for repacketization since the last call to opus_repacketizer_init().
* This means that it must have the same coding mode, audio bandwidth, frame
* size, and channel count.
* This can be checked in advance by examining the top 6 bits of the first
* byte of the packet, and ensuring they match the top 6 bits of the first
* byte of any previously submitted packet.
* The total duration of audio in the repacketizer state also must not exceed
* 120 ms, the maximum duration of a single packet, after adding this packet.
*
* The contents of the current repacketizer state can be extracted into new
* packets using opus_repacketizer_out() or opus_repacketizer_out_range().
*
* In order to add a packet with a different configuration or to add more
* audio beyond 120 ms, you must clear the repacketizer state by calling
* opus_repacketizer_init().
* If a packet is too large to add to the current repacketizer state, no part
* of it is added, even if it contains multiple frames, some of which might
* fit.
* If you wish to be able to add parts of such packets, you should first use
* another repacketizer to split the packet into pieces and add them
* individually.
* @see opus_repacketizer_out_range
* @see opus_repacketizer_out
* @see opus_repacketizer_init
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to which to
* add the packet.
* @param[in] data <tt>const unsigned char*</tt>: The packet data.
* The application must ensure
* this pointer remains valid
* until the next call to
* opus_repacketizer_init() or
* opus_repacketizer_destroy().
* @param len <tt>opus_int32</tt>: The number of bytes in the packet data.
* @returns An error code indicating whether or not the operation succeeded.
* @retval #OPUS_OK The packet's contents have been added to the repacketizer
* state.
* @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence,
* the packet's TOC sequence was not compatible
* with previously submitted packets (because
* the coding mode, audio bandwidth, frame size,
* or channel count did not match), or adding
* this packet would increase the total amount of
* audio stored in the repacketizer state to more
* than 120 ms.
*/
OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
/** Construct a new packet from data previously submitted to the repacketizer
* state via opus_repacketizer_cat().
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
* construct the new packet.
* @param begin <tt>int</tt>: The index of the first frame in the current
* repacketizer state to include in the output.
* @param end <tt>int</tt>: One past the index of the last frame in the
* current repacketizer state to include in the
* output.
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
* store the output packet.
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
* the output buffer. In order to guarantee
* success, this should be at least
* <code>1276</code> for a single frame,
* or for multiple frames,
* <code>1277*(end-begin)</code>.
* However, <code>1*(end-begin)</code> plus
* the size of all packet data submitted to
* the repacketizer since the last call to
* opus_repacketizer_init() or
* opus_repacketizer_create() is also
* sufficient, and possibly much smaller.
* @returns The total size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BAD_ARG <code>[begin,end)</code> was an invalid range of
* frames (begin < 0, begin >= end, or end >
* opus_repacketizer_get_nb_frames()).
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
* complete output packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Return the total number of frames contained in packet data submitted to
* the repacketizer state so far via opus_repacketizer_cat() since the last
* call to opus_repacketizer_init() or opus_repacketizer_create().
* This defines the valid range of packets that can be extracted with
* opus_repacketizer_out_range() or opus_repacketizer_out().
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state containing the
* frames.
* @returns The total number of frames contained in the packet data submitted
* to the repacketizer state.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
/** Construct a new packet from data previously submitted to the repacketizer
* state via opus_repacketizer_cat().
* This is a convenience routine that returns all the data submitted so far
* in a single packet.
* It is equivalent to calling
* @code
* opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp),
* data, maxlen)
* @endcode
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
* construct the new packet.
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
* store the output packet.
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
* the output buffer. In order to guarantee
* success, this should be at least
* <code>1277*opus_repacketizer_get_nb_frames(rp)</code>.
* However,
* <code>1*opus_repacketizer_get_nb_frames(rp)</code>
* plus the size of all packet data
* submitted to the repacketizer since the
* last call to opus_repacketizer_init() or
* opus_repacketizer_create() is also
* sufficient, and possibly much smaller.
* @returns The total size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
* complete output packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1);
/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence).
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to pad.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
* This must be at least as large as len.
* @returns an error code
* @retval #OPUS_OK \a on success.
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len);
/** Remove all padding from a given Opus packet and rewrite the TOC sequence to
* minimize space usage.
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to strip.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @returns The new size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BAD_ARG \a len was less than 1.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len);
/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence).
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to pad.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
* This must be at least 1.
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
* This must be at least as large as len.
* @returns an error code
* @retval #OPUS_OK \a on success.
* @retval #OPUS_BAD_ARG \a len was less than 1.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams);
/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to
* minimize space usage.
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to strip.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
* This must be at least 1.
* @returns The new size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams);
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* OPUS_H */

753
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_defines.h

@ -0,0 +1,753 @@
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
Written by Jean-Marc Valin and Koen Vos */
/*
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.
*/
/**
* @file opus_defines.h
* @brief Opus reference implementation constants
*/
#ifndef OPUS_DEFINES_H
#define OPUS_DEFINES_H
#include "opus_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @defgroup opus_errorcodes Error codes
* @{
*/
/** No error @hideinitializer*/
#define OPUS_OK 0
/** One or more invalid/out of range arguments @hideinitializer*/
#define OPUS_BAD_ARG -1
/** Not enough bytes allocated in the buffer @hideinitializer*/
#define OPUS_BUFFER_TOO_SMALL -2
/** An internal error was detected @hideinitializer*/
#define OPUS_INTERNAL_ERROR -3
/** The compressed data passed is corrupted @hideinitializer*/
#define OPUS_INVALID_PACKET -4
/** Invalid/unsupported request number @hideinitializer*/
#define OPUS_UNIMPLEMENTED -5
/** An encoder or decoder structure is invalid or already freed @hideinitializer*/
#define OPUS_INVALID_STATE -6
/** Memory allocation has failed @hideinitializer*/
#define OPUS_ALLOC_FAIL -7
/**@}*/
/** @cond OPUS_INTERNAL_DOC */
/**Export control for opus functions */
#ifndef OPUS_EXPORT
# if defined(WIN32)
# if defined(OPUS_BUILD) && defined(DLL_EXPORT)
# define OPUS_EXPORT __declspec(dllexport)
# else
# define OPUS_EXPORT
# endif
# elif defined(__GNUC__) && defined(OPUS_BUILD)
# define OPUS_EXPORT __attribute__ ((visibility ("default")))
# else
# define OPUS_EXPORT
# endif
#endif
# if !defined(OPUS_GNUC_PREREQ)
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
# define OPUS_GNUC_PREREQ(_maj,_min) \
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
# else
# define OPUS_GNUC_PREREQ(_maj,_min) 0
# endif
# endif
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
# if OPUS_GNUC_PREREQ(3,0)
# define OPUS_RESTRICT __restrict__
# elif (defined(_MSC_VER) && _MSC_VER >= 1400)
# define OPUS_RESTRICT __restrict
# else
# define OPUS_RESTRICT
# endif
#else
# define OPUS_RESTRICT restrict
#endif
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
# if OPUS_GNUC_PREREQ(2,7)
# define OPUS_INLINE __inline__
# elif (defined(_MSC_VER))
# define OPUS_INLINE __inline
# else
# define OPUS_INLINE
# endif
#else
# define OPUS_INLINE inline
#endif
/**Warning attributes for opus functions
* NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out
* some paranoid null checks. */
#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__))
#else
# define OPUS_WARN_UNUSED_RESULT
#endif
#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x)))
#else
# define OPUS_ARG_NONNULL(_x)
#endif
/** These are the actual Encoder CTL ID numbers.
* They should not be used directly by applications.
* In general, SETs should be even and GETs should be odd.*/
#define OPUS_SET_APPLICATION_REQUEST 4000
#define OPUS_GET_APPLICATION_REQUEST 4001
#define OPUS_SET_BITRATE_REQUEST 4002
#define OPUS_GET_BITRATE_REQUEST 4003
#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004
#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005
#define OPUS_SET_VBR_REQUEST 4006
#define OPUS_GET_VBR_REQUEST 4007
#define OPUS_SET_BANDWIDTH_REQUEST 4008
#define OPUS_GET_BANDWIDTH_REQUEST 4009
#define OPUS_SET_COMPLEXITY_REQUEST 4010
#define OPUS_GET_COMPLEXITY_REQUEST 4011
#define OPUS_SET_INBAND_FEC_REQUEST 4012
#define OPUS_GET_INBAND_FEC_REQUEST 4013
#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014
#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015
#define OPUS_SET_DTX_REQUEST 4016
#define OPUS_GET_DTX_REQUEST 4017
#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020
#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021
#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022
#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023
#define OPUS_SET_SIGNAL_REQUEST 4024
#define OPUS_GET_SIGNAL_REQUEST 4025
#define OPUS_GET_LOOKAHEAD_REQUEST 4027
/* #define OPUS_RESET_STATE 4028 */
#define OPUS_GET_SAMPLE_RATE_REQUEST 4029
#define OPUS_GET_FINAL_RANGE_REQUEST 4031
#define OPUS_GET_PITCH_REQUEST 4033
#define OPUS_SET_GAIN_REQUEST 4034
#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */
#define OPUS_SET_LSB_DEPTH_REQUEST 4036
#define OPUS_GET_LSB_DEPTH_REQUEST 4037
#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039
#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040
#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041
#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042
#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043
/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */
/* Macros to trigger compilation errors when the wrong types are provided to a CTL */
#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x))
#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr)))
#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr)))
#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr)))
/** @endcond */
/** @defgroup opus_ctlvalues Pre-defined values for CTL interface
* @see opus_genericctls, opus_encoderctls
* @{
*/
/* Values for the various encoder CTLs */
#define OPUS_AUTO -1000 /**<Auto/default setting @hideinitializer*/
#define OPUS_BITRATE_MAX -1 /**<Maximum bitrate @hideinitializer*/
/** Best for most VoIP/videoconference applications where listening quality and intelligibility matter most
* @hideinitializer */
#define OPUS_APPLICATION_VOIP 2048
/** Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to the input
* @hideinitializer */
#define OPUS_APPLICATION_AUDIO 2049
/** Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
* @hideinitializer */
#define OPUS_APPLICATION_RESTRICTED_LOWDELAY 2051
#define OPUS_SIGNAL_VOICE 3001 /**< Signal being encoded is voice */
#define OPUS_SIGNAL_MUSIC 3002 /**< Signal being encoded is music */
#define OPUS_BANDWIDTH_NARROWBAND 1101 /**< 4 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_MEDIUMBAND 1102 /**< 6 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_WIDEBAND 1103 /**< 8 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_SUPERWIDEBAND 1104 /**<12 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_FULLBAND 1105 /**<20 kHz bandpass @hideinitializer*/
#define OPUS_FRAMESIZE_ARG 5000 /**< Select frame size from the argument (default) */
#define OPUS_FRAMESIZE_2_5_MS 5001 /**< Use 2.5 ms frames */
#define OPUS_FRAMESIZE_5_MS 5002 /**< Use 5 ms frames */
#define OPUS_FRAMESIZE_10_MS 5003 /**< Use 10 ms frames */
#define OPUS_FRAMESIZE_20_MS 5004 /**< Use 20 ms frames */
#define OPUS_FRAMESIZE_40_MS 5005 /**< Use 40 ms frames */
#define OPUS_FRAMESIZE_60_MS 5006 /**< Use 60 ms frames */
/**@}*/
/** @defgroup opus_encoderctls Encoder related CTLs
*
* These are convenience macros for use with the \c opus_encode_ctl
* interface. They are used to generate the appropriate series of
* arguments for that call, passing the correct type, size and so
* on as expected for each particular request.
*
* Some usage examples:
*
* @code
* int ret;
* ret = opus_encoder_ctl(enc_ctx, OPUS_SET_BANDWIDTH(OPUS_AUTO));
* if (ret != OPUS_OK) return ret;
*
* opus_int32 rate;
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&rate));
*
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
* @endcode
*
* @see opus_genericctls, opus_encoder
* @{
*/
/** Configures the encoder's computational complexity.
* The supported range is 0-10 inclusive with 10 representing the highest complexity.
* @see OPUS_GET_COMPLEXITY
* @param[in] x <tt>opus_int32</tt>: Allowed values: 0-10, inclusive.
*
* @hideinitializer */
#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x)
/** Gets the encoder's complexity configuration.
* @see OPUS_SET_COMPLEXITY
* @param[out] x <tt>opus_int32 *</tt>: Returns a value in the range 0-10,
* inclusive.
* @hideinitializer */
#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x)
/** Configures the bitrate in the encoder.
* Rates from 500 to 512000 bits per second are meaningful, as well as the
* special values #OPUS_AUTO and #OPUS_BITRATE_MAX.
* The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much
* rate as it can, which is useful for controlling the rate by adjusting the
* output buffer size.
* @see OPUS_GET_BITRATE
* @param[in] x <tt>opus_int32</tt>: Bitrate in bits per second. The default
* is determined based on the number of
* channels and the input sampling rate.
* @hideinitializer */
#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x)
/** Gets the encoder's bitrate configuration.
* @see OPUS_SET_BITRATE
* @param[out] x <tt>opus_int32 *</tt>: Returns the bitrate in bits per second.
* The default is determined based on the
* number of channels and the input
* sampling rate.
* @hideinitializer */
#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x)
/** Enables or disables variable bitrate (VBR) in the encoder.
* The configured bitrate may not be met exactly because frames must
* be an integer number of bytes in length.
* @see OPUS_GET_VBR
* @see OPUS_SET_VBR_CONSTRAINT
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Hard CBR. For LPC/hybrid modes at very low bit-rate, this can
* cause noticeable quality degradation.</dd>
* <dt>1</dt><dd>VBR (default). The exact type of VBR is controlled by
* #OPUS_SET_VBR_CONSTRAINT.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x)
/** Determine if variable bitrate (VBR) is enabled in the encoder.
* @see OPUS_SET_VBR
* @see OPUS_GET_VBR_CONSTRAINT
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Hard CBR.</dd>
* <dt>1</dt><dd>VBR (default). The exact type of VBR may be retrieved via
* #OPUS_GET_VBR_CONSTRAINT.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x)
/** Enables or disables constrained VBR in the encoder.
* This setting is ignored when the encoder is in CBR mode.
* @warning Only the MDCT mode of Opus currently heeds the constraint.
* Speech mode ignores it completely, hybrid mode may fail to obey it
* if the LPC layer uses more bitrate than the constraint would have
* permitted.
* @see OPUS_GET_VBR_CONSTRAINT
* @see OPUS_SET_VBR
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Unconstrained VBR.</dd>
* <dt>1</dt><dd>Constrained VBR (default). This creates a maximum of one
* frame of buffering delay assuming a transport with a
* serialization speed of the nominal bitrate.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x)
/** Determine if constrained VBR is enabled in the encoder.
* @see OPUS_SET_VBR_CONSTRAINT
* @see OPUS_GET_VBR
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Unconstrained VBR.</dd>
* <dt>1</dt><dd>Constrained VBR (default).</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x)
/** Configures mono/stereo forcing in the encoder.
* This can force the encoder to produce packets encoded as either mono or
* stereo, regardless of the format of the input audio. This is useful when
* the caller knows that the input signal is currently a mono source embedded
* in a stereo stream.
* @see OPUS_GET_FORCE_CHANNELS
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
* <dt>1</dt> <dd>Forced mono</dd>
* <dt>2</dt> <dd>Forced stereo</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x)
/** Gets the encoder's forced channel configuration.
* @see OPUS_SET_FORCE_CHANNELS
* @param[out] x <tt>opus_int32 *</tt>:
* <dl>
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
* <dt>1</dt> <dd>Forced mono</dd>
* <dt>2</dt> <dd>Forced stereo</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x)
/** Configures the maximum bandpass that the encoder will select automatically.
* Applications should normally use this instead of #OPUS_SET_BANDWIDTH
* (leaving that set to the default, #OPUS_AUTO). This allows the
* application to set an upper bound based on the type of input it is
* providing, but still gives the encoder the freedom to reduce the bandpass
* when the bitrate becomes too low, for better overall quality.
* @see OPUS_GET_MAX_BANDWIDTH
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured maximum allowed bandpass.
* @see OPUS_SET_MAX_BANDWIDTH
* @param[out] x <tt>opus_int32 *</tt>: Allowed values:
* <dl>
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
/** Sets the encoder's bandpass to a specific value.
* This prevents the encoder from automatically selecting the bandpass based
* on the available bitrate. If an application knows the bandpass of the input
* audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH
* instead, which still gives the encoder the freedom to reduce the bandpass
* when the bitrate becomes too low, for better overall quality.
* @see OPUS_GET_BANDWIDTH
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x)
/** Configures the type of signal being encoded.
* This is a hint which helps the encoder's mode selection.
* @see OPUS_GET_SIGNAL
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured signal type.
* @see OPUS_SET_SIGNAL
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's intended application.
* The initial value is a mandatory argument to the encoder_create function.
* @see OPUS_GET_APPLICATION
* @param[in] x <tt>opus_int32</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured application.
* @see OPUS_SET_APPLICATION
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x)
/** Gets the total samples of delay added by the entire codec.
* This can be queried by the encoder and then the provided number of samples can be
* skipped on from the start of the decoder's output to provide time aligned input
* and output. From the perspective of a decoding application the real data begins this many
* samples late.
*
* The decoder contribution to this delay is identical for all decoders, but the
* encoder portion of the delay may vary from implementation to implementation,
* version to version, or even depend on the encoder's initial configuration.
* Applications needing delay compensation should call this CTL rather than
* hard-coding a value.
* @param[out] x <tt>opus_int32 *</tt>: Number of lookahead samples
* @hideinitializer */
#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's use of inband forward error correction (FEC).
* @note This is only applicable to the LPC layer
* @see OPUS_GET_INBAND_FEC
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Disable inband FEC (default).</dd>
* <dt>1</dt><dd>Enable inband FEC.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x)
/** Gets encoder's configured use of inband forward error correction.
* @see OPUS_SET_INBAND_FEC
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Inband FEC disabled (default).</dd>
* <dt>1</dt><dd>Inband FEC enabled.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's expected packet loss percentage.
* Higher values trigger progressively more loss resistant behavior in the encoder
* at the expense of quality at a given bitrate in the absence of packet loss, but
* greater quality under loss.
* @see OPUS_GET_PACKET_LOSS_PERC
* @param[in] x <tt>opus_int32</tt>: Loss percentage in the range 0-100, inclusive (default: 0).
* @hideinitializer */
#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured packet loss percentage.
* @see OPUS_SET_PACKET_LOSS_PERC
* @param[out] x <tt>opus_int32 *</tt>: Returns the configured loss percentage
* in the range 0-100, inclusive (default: 0).
* @hideinitializer */
#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's use of discontinuous transmission (DTX).
* @note This is only applicable to the LPC layer
* @see OPUS_GET_DTX
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Disable DTX (default).</dd>
* <dt>1</dt><dd>Enabled DTX.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x)
/** Gets encoder's configured use of discontinuous transmission.
* @see OPUS_SET_DTX
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>DTX disabled (default).</dd>
* <dt>1</dt><dd>DTX enabled.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x)
/** Configures the depth of signal being encoded.
*
* This is a hint which helps the encoder identify silence and near-silence.
* It represents the number of significant bits of linear intensity below
* which the signal contains ignorable quantization or other noise.
*
* For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting
* for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate
* for 16-bit linear pcm input with opus_encode_float().
*
* When using opus_encode() instead of opus_encode_float(), or when libopus
* is compiled for fixed-point, the encoder uses the minimum of the value
* set here and the value 16.
*
* @see OPUS_GET_LSB_DEPTH
* @param[in] x <tt>opus_int32</tt>: Input precision in bits, between 8 and 24
* (default: 24).
* @hideinitializer */
#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured signal depth.
* @see OPUS_SET_LSB_DEPTH
* @param[out] x <tt>opus_int32 *</tt>: Input precision in bits, between 8 and
* 24 (default: 24).
* @hideinitializer */
#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's use of variable duration frames.
* When variable duration is enabled, the encoder is free to use a shorter frame
* size than the one requested in the opus_encode*() call.
* It is then the user's responsibility
* to verify how much audio was encoded by checking the ToC byte of the encoded
* packet. The part of the audio that was not encoded needs to be resent to the
* encoder for the next call. Do not use this option unless you <b>really</b>
* know what you are doing.
* @see OPUS_GET_EXPERT_FRAME_DURATION
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_VARIABLE</dt><dd>Optimize the frame size dynamically.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured use of variable duration frames.
* @see OPUS_SET_EXPERT_FRAME_DURATION
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_VARIABLE</dt><dd>Optimize the frame size dynamically.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x)
/** If set to 1, disables almost all use of prediction, making frames almost
* completely independent. This reduces quality.
* @see OPUS_GET_PREDICTION_DISABLED
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Enable prediction (default).</dd>
* <dt>1</dt><dd>Disable prediction.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured prediction status.
* @see OPUS_SET_PREDICTION_DISABLED
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Prediction enabled (default).</dd>
* <dt>1</dt><dd>Prediction disabled.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x)
/**@}*/
/** @defgroup opus_genericctls Generic CTLs
*
* These macros are used with the \c opus_decoder_ctl and
* \c opus_encoder_ctl calls to generate a particular
* request.
*
* When called on an \c OpusDecoder they apply to that
* particular decoder instance. When called on an
* \c OpusEncoder they apply to the corresponding setting
* on that encoder instance, if present.
*
* Some usage examples:
*
* @code
* int ret;
* opus_int32 pitch;
* ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch));
* if (ret == OPUS_OK) return ret;
*
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
* opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE);
*
* opus_int32 enc_bw, dec_bw;
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw));
* opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw));
* if (enc_bw != dec_bw) {
* printf("packet bandwidth mismatch!\n");
* }
* @endcode
*
* @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls
* @{
*/
/** Resets the codec state to be equivalent to a freshly initialized state.
* This should be called when switching streams in order to prevent
* the back to back decoding from giving different results from
* one at a time decoding.
* @hideinitializer */
#define OPUS_RESET_STATE 4028
/** Gets the final state of the codec's entropy coder.
* This is used for testing purposes,
* The encoder and decoder state should be identical after coding a payload
* (assuming no data corruption or software bugs)
*
* @param[out] x <tt>opus_uint32 *</tt>: Entropy coder state
*
* @hideinitializer */
#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x)
/** Gets the encoder's configured bandpass or the decoder's last bandpass.
* @see OPUS_SET_BANDWIDTH
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
/** Gets the sampling rate the encoder or decoder was initialized with.
* This simply returns the <code>Fs</code> value passed to opus_encoder_init()
* or opus_decoder_init().
* @param[out] x <tt>opus_int32 *</tt>: Sampling rate of encoder or decoder.
* @hideinitializer
*/
#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x)
/**@}*/
/** @defgroup opus_decoderctls Decoder related CTLs
* @see opus_genericctls, opus_encoderctls, opus_decoder
* @{
*/
/** Configures decoder gain adjustment.
* Scales the decoded output by a factor specified in Q8 dB units.
* This has a maximum range of -32768 to 32767 inclusive, and returns
* OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment.
* This setting survives decoder reset.
*
* gain = pow(10, x/(20.0*256))
*
* @param[in] x <tt>opus_int32</tt>: Amount to scale PCM signal by in Q8 dB units.
* @hideinitializer */
#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x)
/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN
*
* @param[out] x <tt>opus_int32 *</tt>: Amount to scale PCM signal by in Q8 dB units.
* @hideinitializer */
#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x)
/** Gets the duration (in samples) of the last packet successfully decoded or concealed.
* @param[out] x <tt>opus_int32 *</tt>: Number of samples (at current sampling rate).
* @hideinitializer */
#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x)
/** Gets the pitch of the last decoded frame, if available.
* This can be used for any post-processing algorithm requiring the use of pitch,
* e.g. time stretching/shortening. If the last frame was not voiced, or if the
* pitch was not coded in the frame, then zero is returned.
*
* This CTL is only implemented for decoder instances.
*
* @param[out] x <tt>opus_int32 *</tt>: pitch period at 48 kHz (or 0 if not available)
*
* @hideinitializer */
#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x)
/**@}*/
/** @defgroup opus_libinfo Opus library information functions
* @{
*/
/** Converts an opus error code into a human readable string.
*
* @param[in] error <tt>int</tt>: Error number
* @returns Error string
*/
OPUS_EXPORT const char *opus_strerror(int error);
/** Gets the libopus version string.
*
* Applications may look for the substring "-fixed" in the version string to
* determine whether they have a fixed-point or floating-point build at
* runtime.
*
* @returns Version string
*/
OPUS_EXPORT const char *opus_get_version_string(void);
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* OPUS_DEFINES_H */

660
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_multistream.h

@ -0,0 +1,660 @@
/* Copyright (c) 2011 Xiph.Org Foundation
Written by Jean-Marc Valin */
/*
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.
*/
/**
* @file opus_multistream.h
* @brief Opus reference implementation multistream API
*/
#ifndef OPUS_MULTISTREAM_H
#define OPUS_MULTISTREAM_H
#include "opus.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @cond OPUS_INTERNAL_DOC */
/** Macros to trigger compilation errors when the wrong types are provided to a
* CTL. */
/**@{*/
#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr)))
#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr)))
/**@}*/
/** These are the actual encoder and decoder CTL ID numbers.
* They should not be used directly by applications.
* In general, SETs should be even and GETs should be odd.*/
/**@{*/
#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120
#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122
/**@}*/
/** @endcond */
/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs
*
* These are convenience macros that are specific to the
* opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl()
* interface.
* The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and
* @ref opus_decoderctls may be applied to a multistream encoder or decoder as
* well.
* In addition, you may retrieve the encoder or decoder state for an specific
* stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or
* #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually.
*/
/**@{*/
/** Gets the encoder state for an individual stream of a multistream encoder.
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose encoder you
* wish to retrieve.
* This must be non-negative and less than
* the <code>streams</code> parameter used
* to initialize the encoder.
* @param[out] y <tt>OpusEncoder**</tt>: Returns a pointer to the given
* encoder state.
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
* @hideinitializer
*/
#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y)
/** Gets the decoder state for an individual stream of a multistream decoder.
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose decoder you
* wish to retrieve.
* This must be non-negative and less than
* the <code>streams</code> parameter used
* to initialize the decoder.
* @param[out] y <tt>OpusDecoder**</tt>: Returns a pointer to the given
* decoder state.
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
* @hideinitializer
*/
#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y)
/**@}*/
/** @defgroup opus_multistream Opus Multistream API
* @{
*
* The multistream API allows individual Opus streams to be combined into a
* single packet, enabling support for up to 255 channels. Unlike an
* elementary Opus stream, the encoder and decoder must negotiate the channel
* configuration before the decoder can successfully interpret the data in the
* packets produced by the encoder. Some basic information, such as packet
* duration, can be computed without any special negotiation.
*
* The format for multistream Opus packets is defined in
* <a href="https://tools.ietf.org/html/rfc7845">RFC 7845</a>
* and is based on the self-delimited Opus framing described in Appendix B of
* <a href="https://tools.ietf.org/html/rfc6716">RFC 6716</a>.
* Normal Opus packets are just a degenerate case of multistream Opus packets,
* and can be encoded or decoded with the multistream API by setting
* <code>streams</code> to <code>1</code> when initializing the encoder or
* decoder.
*
* Multistream Opus streams can contain up to 255 elementary Opus streams.
* These may be either "uncoupled" or "coupled", indicating that the decoder
* is configured to decode them to either 1 or 2 channels, respectively.
* The streams are ordered so that all coupled streams appear at the
* beginning.
*
* A <code>mapping</code> table defines which decoded channel <code>i</code>
* should be used for each input/output (I/O) channel <code>j</code>. This table is
* typically provided as an unsigned char array.
* Let <code>i = mapping[j]</code> be the index for I/O channel <code>j</code>.
* If <code>i < 2*coupled_streams</code>, then I/O channel <code>j</code> is
* encoded as the left channel of stream <code>(i/2)</code> if <code>i</code>
* is even, or as the right channel of stream <code>(i/2)</code> if
* <code>i</code> is odd. Otherwise, I/O channel <code>j</code> is encoded as
* mono in stream <code>(i - coupled_streams)</code>, unless it has the special
* value 255, in which case it is omitted from the encoding entirely (the
* decoder will reproduce it as silence). Each value <code>i</code> must either
* be the special value 255 or be less than <code>streams + coupled_streams</code>.
*
* The output channels specified by the encoder
* should use the
* <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
* channel ordering</a>. A decoder may wish to apply an additional permutation
* to the mapping the encoder used to achieve a different output channel
* order (e.g. for outputing in WAV order).
*
* Each multistream packet contains an Opus packet for each stream, and all of
* the Opus packets in a single multistream packet must have the same
* duration. Therefore the duration of a multistream packet can be extracted
* from the TOC sequence of the first stream, which is located at the
* beginning of the packet, just like an elementary Opus stream:
*
* @code
* int nb_samples;
* int nb_frames;
* nb_frames = opus_packet_get_nb_frames(data, len);
* if (nb_frames < 1)
* return nb_frames;
* nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames;
* @endcode
*
* The general encoding and decoding process proceeds exactly the same as in
* the normal @ref opus_encoder and @ref opus_decoder APIs.
* See their documentation for an overview of how to use the corresponding
* multistream functions.
*/
/** Opus multistream encoder state.
* This contains the complete state of a multistream Opus encoder.
* It is position independent and can be freely copied.
* @see opus_multistream_encoder_create
* @see opus_multistream_encoder_init
*/
typedef struct OpusMSEncoder OpusMSEncoder;
/** Opus multistream decoder state.
* This contains the complete state of a multistream Opus decoder.
* It is position independent and can be freely copied.
* @see opus_multistream_decoder_create
* @see opus_multistream_decoder_init
*/
typedef struct OpusMSDecoder OpusMSDecoder;
/**\name Multistream encoder functions */
/**@{*/
/** Gets the size of an OpusMSEncoder structure.
* @param streams <tt>int</tt>: The total number of streams to encode from the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
* to encode.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* encoded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @returns The size in bytes on success, or a negative error code
* (see @ref opus_errorcodes) on error.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size(
int streams,
int coupled_streams
);
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size(
int channels,
int mapping_family
);
/** Allocates and initializes a multistream encoder state.
* Call opus_multistream_encoder_destroy() to release
* this object when finished.
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels in the input signal.
* This must be at most 255.
* It may be greater than the number of
* coded channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams to encode from the
* input.
* This must be no more than the number of channels.
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
* to encode.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* encoded channels (<code>streams +
* coupled_streams</code>) must be no
* more than the number of input channels.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* encoded channels to input channels, as described in
* @ref opus_multistream. As an extra constraint, the
* multistream encoder does not allow encoding coupled
* streams for which one channel is unused since this
* is never a good idea.
* @param application <tt>int</tt>: The target encoder application.
* This must be one of the following:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
* code (see @ref opus_errorcodes) on
* failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create(
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping,
int application,
int *error
) OPUS_ARG_NONNULL(5);
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create(
opus_int32 Fs,
int channels,
int mapping_family,
int *streams,
int *coupled_streams,
unsigned char *mapping,
int application,
int *error
) OPUS_ARG_NONNULL(5);
/** Initialize a previously allocated multistream encoder state.
* The memory pointed to by \a st must be at least the size returned by
* opus_multistream_encoder_get_size().
* This is intended for applications which use their own allocator instead of
* malloc.
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @see opus_multistream_encoder_create
* @see opus_multistream_encoder_get_size
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels in the input signal.
* This must be at most 255.
* It may be greater than the number of
* coded channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams to encode from the
* input.
* This must be no more than the number of channels.
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
* to encode.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* encoded channels (<code>streams +
* coupled_streams</code>) must be no
* more than the number of input channels.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* encoded channels to input channels, as described in
* @ref opus_multistream. As an extra constraint, the
* multistream encoder does not allow encoding coupled
* streams for which one channel is unused since this
* is never a good idea.
* @param application <tt>int</tt>: The target encoder application.
* This must be one of the following:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
* on failure.
*/
OPUS_EXPORT int opus_multistream_encoder_init(
OpusMSEncoder *st,
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping,
int application
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
OPUS_EXPORT int opus_multistream_surround_encoder_init(
OpusMSEncoder *st,
opus_int32 Fs,
int channels,
int mapping_family,
int *streams,
int *coupled_streams,
unsigned char *mapping,
int application
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
/** Encodes a multistream Opus frame.
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
* @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
* samples.
* This must contain
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
* signal.
* This must be an Opus frame size for the
* encoder's sampling rate.
* For example, at 48 kHz the permitted values
* are 120, 240, 480, 960, 1920, and 2880.
* Passing in a duration of less than 10 ms
* (480 samples at 48 kHz) will prevent the
* encoder from using the LPC or hybrid modes.
* @param[out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode(
OpusMSEncoder *st,
const opus_int16 *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Encodes a multistream Opus frame from floating point input.
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
* @param[in] pcm <tt>const float*</tt>: The input signal as interleaved
* samples with a normal range of
* +/-1.0.
* Samples with a range beyond +/-1.0
* are supported but will be clipped by
* decoders using the integer API and
* should only be used if it is known
* that the far end supports extended
* dynamic range.
* This must contain
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
* signal.
* This must be an Opus frame size for the
* encoder's sampling rate.
* For example, at 48 kHz the permitted values
* are 120, 240, 480, 960, 1920, and 2880.
* Passing in a duration of less than 10 ms
* (480 samples at 48 kHz) will prevent the
* encoder from using the LPC or hybrid modes.
* @param[out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float(
OpusMSEncoder *st,
const float *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Frees an <code>OpusMSEncoder</code> allocated by
* opus_multistream_encoder_create().
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to be freed.
*/
OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st);
/** Perform a CTL function on a multistream Opus encoder.
*
* Generally the request and subsequent arguments are generated by a
* convenience macro.
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls,
* @ref opus_encoderctls, or @ref opus_multistream_ctls.
* @see opus_genericctls
* @see opus_encoderctls
* @see opus_multistream_ctls
*/
OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/**@}*/
/**\name Multistream decoder functions */
/**@{*/
/** Gets the size of an <code>OpusMSDecoder</code> structure.
* @param streams <tt>int</tt>: The total number of streams coded in the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number streams to decode as coupled
* (2 channel) streams.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* coded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @returns The size in bytes on success, or a negative error code
* (see @ref opus_errorcodes) on error.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size(
int streams,
int coupled_streams
);
/** Allocates and initializes a multistream decoder state.
* Call opus_multistream_decoder_destroy() to release
* this object when finished.
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels to output.
* This must be at most 255.
* It may be different from the number of coded
* channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams coded in the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
* (2 channel) streams.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* coded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* coded channels to output channels, as described in
* @ref opus_multistream.
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
* code (see @ref opus_errorcodes) on
* failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create(
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping,
int *error
) OPUS_ARG_NONNULL(5);
/** Intialize a previously allocated decoder state object.
* The memory pointed to by \a st must be at least the size returned by
* opus_multistream_encoder_get_size().
* This is intended for applications which use their own allocator instead of
* malloc.
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @see opus_multistream_decoder_create
* @see opus_multistream_deocder_get_size
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels to output.
* This must be at most 255.
* It may be different from the number of coded
* channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams coded in the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
* (2 channel) streams.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* coded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* coded channels to output channels, as described in
* @ref opus_multistream.
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
* on failure.
*/
OPUS_EXPORT int opus_multistream_decoder_init(
OpusMSDecoder *st,
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
/** Decode a multistream Opus packet.
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
* Use a <code>NULL</code>
* pointer to indicate packet
* loss.
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
* samples.
* This must contain room for
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: The number of samples per channel of
* available space in \a pcm.
* If this is less than the maximum packet duration
* (120 ms; 5760 for 48kHz), this function will not be capable
* of decoding some packets. In the case of PLC (data==NULL)
* or FEC (decode_fec=1), then frame_size needs to be exactly
* the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the
* next incoming packet. For the PLC and FEC cases, frame_size
* <b>must</b> be a multiple of 2.5 ms.
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
* forward error correction data be decoded.
* If no such data is available, the frame is
* decoded as if it were lost.
* @returns Number of samples decoded on success or a negative error code
* (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode(
OpusMSDecoder *st,
const unsigned char *data,
opus_int32 len,
opus_int16 *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Decode a multistream Opus packet with floating point output.
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
* Use a <code>NULL</code>
* pointer to indicate packet
* loss.
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
* samples.
* This must contain room for
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: The number of samples per channel of
* available space in \a pcm.
* If this is less than the maximum packet duration
* (120 ms; 5760 for 48kHz), this function will not be capable
* of decoding some packets. In the case of PLC (data==NULL)
* or FEC (decode_fec=1), then frame_size needs to be exactly
* the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the
* next incoming packet. For the PLC and FEC cases, frame_size
* <b>must</b> be a multiple of 2.5 ms.
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
* forward error correction data be decoded.
* If no such data is available, the frame is
* decoded as if it were lost.
* @returns Number of samples decoded on success or a negative error code
* (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float(
OpusMSDecoder *st,
const unsigned char *data,
opus_int32 len,
float *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Perform a CTL function on a multistream Opus decoder.
*
* Generally the request and subsequent arguments are generated by a
* convenience macro.
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls,
* @ref opus_decoderctls, or @ref opus_multistream_ctls.
* @see opus_genericctls
* @see opus_decoderctls
* @see opus_multistream_ctls
*/
OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/** Frees an <code>OpusMSDecoder</code> allocated by
* opus_multistream_decoder_create().
* @param st <tt>OpusMSDecoder</tt>: Multistream decoder state to be freed.
*/
OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st);
/**@}*/
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* OPUS_MULTISTREAM_H */

159
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_types.h

@ -0,0 +1,159 @@
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
/* Modified by Jean-Marc Valin */
/*
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.
*/
/* opus_types.h based on ogg_types.h from libogg */
/**
@file opus_types.h
@brief Opus reference implementation types
*/
#ifndef OPUS_TYPES_H
#define OPUS_TYPES_H
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
#if (defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
#include <stdint.h>
typedef int16_t opus_int16;
typedef uint16_t opus_uint16;
typedef int32_t opus_int32;
typedef uint32_t opus_uint32;
#elif defined(_WIN32)
# if defined(__CYGWIN__)
# include <_G_config.h>
typedef _G_int32_t opus_int32;
typedef _G_uint32_t opus_uint32;
typedef _G_int16 opus_int16;
typedef _G_uint16 opus_uint16;
# elif defined(__MINGW32__)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
# elif defined(__MWERKS__)
typedef int opus_int32;
typedef unsigned int opus_uint32;
typedef short opus_int16;
typedef unsigned short opus_uint16;
# else
/* MSVC/Borland */
typedef __int32 opus_int32;
typedef unsigned __int32 opus_uint32;
typedef __int16 opus_int16;
typedef unsigned __int16 opus_uint16;
# endif
#elif defined(__MACOS__)
# include <sys/types.h>
typedef SInt16 opus_int16;
typedef UInt16 opus_uint16;
typedef SInt32 opus_int32;
typedef UInt32 opus_uint32;
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
# include <sys/types.h>
typedef int16_t opus_int16;
typedef u_int16_t opus_uint16;
typedef int32_t opus_int32;
typedef u_int32_t opus_uint32;
#elif defined(__BEOS__)
/* Be */
# include <inttypes.h>
typedef int16 opus_int16;
typedef u_int16 opus_uint16;
typedef int32_t opus_int32;
typedef u_int32_t opus_uint32;
#elif defined (__EMX__)
/* OS/2 GCC */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#elif defined (DJGPP)
/* DJGPP */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#elif defined(R5900)
/* PS2 EE */
typedef int opus_int32;
typedef unsigned opus_uint32;
typedef short opus_int16;
typedef unsigned short opus_uint16;
#elif defined(__SYMBIAN32__)
/* Symbian GCC */
typedef signed short opus_int16;
typedef unsigned short opus_uint16;
typedef signed int opus_int32;
typedef unsigned int opus_uint32;
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef long opus_int32;
typedef unsigned long opus_uint32;
#elif defined(CONFIG_TI_C6X)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#else
/* Give up, take a reasonable guess */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#endif
#define opus_int int /* used for counters etc; at least 16 bits */
#define opus_int64 long long
#define opus_int8 signed char
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
#define opus_uint64 unsigned long long
#define opus_uint8 unsigned char
#endif /* OPUS_TYPES_H */

BIN
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Info.plist

Binary file not shown.

6
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Modules/module.modulemap

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

BIN
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/opus

Binary file not shown.

981
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus.h

@ -0,0 +1,981 @@
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
Written by Jean-Marc Valin and Koen Vos */
/*
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.
*/
/**
* @file opus.h
* @brief Opus reference implementation API
*/
#ifndef OPUS_H
#define OPUS_H
#include "opus_types.h"
#include "opus_defines.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @mainpage Opus
*
* The Opus codec is designed for interactive speech and audio transmission over the Internet.
* It is designed by the IETF Codec Working Group and incorporates technology from
* Skype's SILK codec and Xiph.Org's CELT codec.
*
* The Opus codec is designed to 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. Its main features are:
* @li Sampling rates from 8 to 48 kHz
* @li Bit-rates from 6 kb/s to 510 kb/s
* @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR)
* @li Audio bandwidth from narrowband to full-band
* @li Support for speech and music
* @li Support for mono and stereo
* @li Support for multichannel (up to 255 channels)
* @li Frame sizes from 2.5 ms to 60 ms
* @li Good loss robustness and packet loss concealment (PLC)
* @li Floating point and fixed-point implementation
*
* Documentation sections:
* @li @ref opus_encoder
* @li @ref opus_decoder
* @li @ref opus_repacketizer
* @li @ref opus_multistream
* @li @ref opus_libinfo
* @li @ref opus_custom
*/
/** @defgroup opus_encoder Opus Encoder
* @{
*
* @brief This page describes the process and functions used to encode Opus.
*
* Since Opus is a stateful codec, the encoding process starts with creating an encoder
* state. This can be done with:
*
* @code
* int error;
* OpusEncoder *enc;
* enc = opus_encoder_create(Fs, channels, application, &error);
* @endcode
*
* From this point, @c enc can be used for encoding an audio stream. An encoder state
* @b must @b not be used for more than one stream at the same time. Similarly, the encoder
* state @b must @b not be re-initialized for each frame.
*
* While opus_encoder_create() allocates memory for the state, it's also possible
* to initialize pre-allocated memory:
*
* @code
* int size;
* int error;
* OpusEncoder *enc;
* size = opus_encoder_get_size(channels);
* enc = malloc(size);
* error = opus_encoder_init(enc, Fs, channels, application);
* @endcode
*
* where opus_encoder_get_size() returns the required size for the encoder state. Note that
* future versions of this code may change the size, so no assuptions should be made about it.
*
* The encoder state is always continuous in memory and only a shallow copy is sufficient
* to copy it (e.g. memcpy())
*
* It is possible to change some of the encoder's settings using the opus_encoder_ctl()
* interface. All these settings already default to the recommended value, so they should
* only be changed when necessary. The most common settings one may want to change are:
*
* @code
* opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
* opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
* opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type));
* @endcode
*
* where
*
* @arg bitrate is in bits per second (b/s)
* @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest
* @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC
*
* See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream.
*
* To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data:
* @code
* len = opus_encode(enc, audio_frame, frame_size, packet, max_packet);
* @endcode
*
* where
* <ul>
* <li>audio_frame is the audio data in opus_int16 (or float for opus_encode_float())</li>
* <li>frame_size is the duration of the frame in samples (per channel)</li>
* <li>packet is the byte array to which the compressed data is written</li>
* <li>max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended).
* Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.</li>
* </ul>
*
* opus_encode() and opus_encode_float() return the number of bytes actually written to the packet.
* The return value <b>can be negative</b>, which indicates that an error has occurred. If the return value
* is 2 bytes or less, then the packet does not need to be transmitted (DTX).
*
* Once the encoder state if no longer needed, it can be destroyed with
*
* @code
* opus_encoder_destroy(enc);
* @endcode
*
* If the encoder was created with opus_encoder_init() rather than opus_encoder_create(),
* then no action is required aside from potentially freeing the memory that was manually
* allocated for it (calling free(enc) for the example above)
*
*/
/** Opus encoder state.
* This contains the complete state of an Opus encoder.
* It is position independent and can be freely copied.
* @see opus_encoder_create,opus_encoder_init
*/
typedef struct OpusEncoder OpusEncoder;
/** Gets the size of an <code>OpusEncoder</code> structure.
* @param[in] channels <tt>int</tt>: Number of channels.
* This must be 1 or 2.
* @returns The size in bytes.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels);
/**
*/
/** Allocates and initializes an encoder state.
* There are three coding modes:
*
* @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice
* signals. It enhances the input signal by high-pass filtering and
* emphasizing formants and harmonics. Optionally it includes in-band
* forward error correction to protect against packet loss. Use this
* mode for typical VoIP applications. Because of the enhancement,
* even at high bitrates the output may sound different from the input.
*
* @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most
* non-voice signals like music. Use this mode for music and mixed
* (music/voice) content, broadcast, and applications requiring less
* than 15 ms of coding delay.
*
* @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that
* disables the speech-optimized mode in exchange for slightly reduced delay.
* This mode can only be set on an newly initialized or freshly reset encoder
* because it changes the codec delay.
*
* This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution).
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
* @param [in] application <tt>int</tt>: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY)
* @param [out] error <tt>int*</tt>: @ref opus_errorcodes
* @note Regardless of the sampling rate and number channels selected, the Opus encoder
* can switch to a lower audio bandwidth or number of channels if the bitrate
* selected is too low. This also means that it is safe to always use 48 kHz stereo input
* and let the encoder optimize the encoding.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create(
opus_int32 Fs,
int channels,
int application,
int *error
);
/** Initializes a previously allocated encoder state
* The memory pointed to by st must be at least the size returned by opus_encoder_get_size().
* This is intended for applications which use their own allocator instead of malloc.
* @see opus_encoder_create(),opus_encoder_get_size()
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate of input signal (Hz)
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) in input signal
* @param [in] application <tt>int</tt>: Coding mode (OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO/OPUS_APPLICATION_RESTRICTED_LOWDELAY)
* @retval #OPUS_OK Success or @ref opus_errorcodes
*/
OPUS_EXPORT int opus_encoder_init(
OpusEncoder *st,
opus_int32 Fs,
int channels,
int application
) OPUS_ARG_NONNULL(1);
/** Encodes an Opus frame.
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
* @param [in] pcm <tt>opus_int16*</tt>: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16)
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
* input signal.
* This must be an Opus frame size for
* the encoder's sampling rate.
* For example, at 48 kHz the permitted
* values are 120, 240, 480, 960, 1920,
* and 2880.
* Passing in a duration of less than
* 10 ms (480 samples at 48 kHz) will
* prevent the encoder from using the LPC
* or hybrid modes.
* @param [out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode(
OpusEncoder *st,
const opus_int16 *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Encodes an Opus frame from floating point input.
* @param [in] st <tt>OpusEncoder*</tt>: Encoder state
* @param [in] pcm <tt>float*</tt>: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0.
* Samples with a range beyond +/-1.0 are supported but will
* be clipped by decoders using the integer API and should
* only be used if it is known that the far end supports
* extended dynamic range.
* length is frame_size*channels*sizeof(float)
* @param [in] frame_size <tt>int</tt>: Number of samples per channel in the
* input signal.
* This must be an Opus frame size for
* the encoder's sampling rate.
* For example, at 48 kHz the permitted
* values are 120, 240, 480, 960, 1920,
* and 2880.
* Passing in a duration of less than
* 10 ms (480 samples at 48 kHz) will
* prevent the encoder from using the LPC
* or hybrid modes.
* @param [out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float(
OpusEncoder *st,
const float *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Frees an <code>OpusEncoder</code> allocated by opus_encoder_create().
* @param[in] st <tt>OpusEncoder*</tt>: State to be freed.
*/
OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st);
/** Perform a CTL function on an Opus encoder.
*
* Generally the request and subsequent arguments are generated
* by a convenience macro.
* @param st <tt>OpusEncoder*</tt>: Encoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls or
* @ref opus_encoderctls.
* @see opus_genericctls
* @see opus_encoderctls
*/
OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/**@}*/
/** @defgroup opus_decoder Opus Decoder
* @{
*
* @brief This page describes the process and functions used to decode Opus.
*
* The decoding process also starts with creating a decoder
* state. This can be done with:
* @code
* int error;
* OpusDecoder *dec;
* dec = opus_decoder_create(Fs, channels, &error);
* @endcode
* where
* @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000
* @li channels is the number of channels (1 or 2)
* @li error will hold the error code in case of failure (or #OPUS_OK on success)
* @li the return value is a newly created decoder state to be used for decoding
*
* While opus_decoder_create() allocates memory for the state, it's also possible
* to initialize pre-allocated memory:
* @code
* int size;
* int error;
* OpusDecoder *dec;
* size = opus_decoder_get_size(channels);
* dec = malloc(size);
* error = opus_decoder_init(dec, Fs, channels);
* @endcode
* where opus_decoder_get_size() returns the required size for the decoder state. Note that
* future versions of this code may change the size, so no assuptions should be made about it.
*
* The decoder state is always continuous in memory and only a shallow copy is sufficient
* to copy it (e.g. memcpy())
*
* To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data:
* @code
* frame_size = opus_decode(dec, packet, len, decoded, max_size, 0);
* @endcode
* where
*
* @li packet is the byte array containing the compressed data
* @li len is the exact number of bytes contained in the packet
* @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float())
* @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array
*
* opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet.
* If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio
* buffer is too small to hold the decoded audio.
*
* Opus is a stateful codec with overlapping blocks and as a result Opus
* packets are not coded independently of each other. Packets must be
* passed into the decoder serially and in the correct order for a correct
* decode. Lost packets can be replaced with loss concealment by calling
* the decoder with a null pointer and zero length for the missing packet.
*
* A single codec state may only be accessed from a single thread at
* a time and any required locking must be performed by the caller. Separate
* streams must be decoded with separate decoder states and can be decoded
* in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK
* defined.
*
*/
/** Opus decoder state.
* This contains the complete state of an Opus decoder.
* It is position independent and can be freely copied.
* @see opus_decoder_create,opus_decoder_init
*/
typedef struct OpusDecoder OpusDecoder;
/** Gets the size of an <code>OpusDecoder</code> structure.
* @param [in] channels <tt>int</tt>: Number of channels.
* This must be 1 or 2.
* @returns The size in bytes.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels);
/** Allocates and initializes a decoder state.
* @param [in] Fs <tt>opus_int32</tt>: Sample rate to decode at (Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
* @param [out] error <tt>int*</tt>: #OPUS_OK Success or @ref opus_errorcodes
*
* Internally Opus stores data at 48000 Hz, so that should be the default
* value for Fs. However, the decoder can efficiently decode to buffers
* at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use
* data at the full sample rate, or knows the compressed data doesn't
* use the full frequency range, it can request decoding at a reduced
* rate. Likewise, the decoder is capable of filling in either mono or
* interleaved stereo pcm buffers, at the caller's request.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create(
opus_int32 Fs,
int channels,
int *error
);
/** Initializes a previously allocated decoder state.
* The state must be at least the size returned by opus_decoder_get_size().
* This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state.
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate to decode to (Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param [in] channels <tt>int</tt>: Number of channels (1 or 2) to decode
* @retval #OPUS_OK Success or @ref opus_errorcodes
*/
OPUS_EXPORT int opus_decoder_init(
OpusDecoder *st,
opus_int32 Fs,
int channels
) OPUS_ARG_NONNULL(1);
/** Decode an Opus packet.
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload*
* @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
* is frame_size*channels*sizeof(opus_int16)
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
* decoded. If no such data is available, the frame is decoded as if it were lost.
* @returns Number of decoded samples or @ref opus_errorcodes
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode(
OpusDecoder *st,
const unsigned char *data,
opus_int32 len,
opus_int16 *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Decode an Opus packet with floating point output.
* @param [in] st <tt>OpusDecoder*</tt>: Decoder state
* @param [in] data <tt>char*</tt>: Input payload. Use a NULL pointer to indicate packet loss
* @param [in] len <tt>opus_int32</tt>: Number of bytes in payload
* @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
* is frame_size*channels*sizeof(float)
* @param [in] frame_size Number of samples per channel of available space in \a pcm.
* If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will
* not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1),
* then frame_size needs to be exactly the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the next incoming packet. For the PLC and
* FEC cases, frame_size <b>must</b> be a multiple of 2.5 ms.
* @param [in] decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band forward error correction data be
* decoded. If no such data is available the frame is decoded as if it were lost.
* @returns Number of decoded samples or @ref opus_errorcodes
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float(
OpusDecoder *st,
const unsigned char *data,
opus_int32 len,
float *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Perform a CTL function on an Opus decoder.
*
* Generally the request and subsequent arguments are generated
* by a convenience macro.
* @param st <tt>OpusDecoder*</tt>: Decoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls or
* @ref opus_decoderctls.
* @see opus_genericctls
* @see opus_decoderctls
*/
OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/** Frees an <code>OpusDecoder</code> allocated by opus_decoder_create().
* @param[in] st <tt>OpusDecoder*</tt>: State to be freed.
*/
OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);
/** Parse an opus packet into one or more frames.
* Opus_decode will perform this operation internally so most applications do
* not need to use this function.
* This function does not copy the frames, the returned pointers are pointers into
* the input packet.
* @param [in] data <tt>char*</tt>: Opus packet to be parsed
* @param [in] len <tt>opus_int32</tt>: size of data
* @param [out] out_toc <tt>char*</tt>: TOC pointer
* @param [out] frames <tt>char*[48]</tt> encapsulated frames
* @param [out] size <tt>opus_int16[48]</tt> sizes of the encapsulated frames
* @param [out] payload_offset <tt>int*</tt>: returns the position of the payload within the packet (in bytes)
* @returns number of frames
*/
OPUS_EXPORT int opus_packet_parse(
const unsigned char *data,
opus_int32 len,
unsigned char *out_toc,
const unsigned char *frames[48],
opus_int16 size[48],
int *payload_offset
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Gets the bandwidth of an Opus packet.
* @param [in] data <tt>char*</tt>: Opus packet
* @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass)
* @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass)
* @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass)
* @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass)
* @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass)
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1);
/** Gets the number of samples per frame from an Opus packet.
* @param [in] data <tt>char*</tt>: Opus packet.
* This must contain at least one byte of
* data.
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
* This must be a multiple of 400, or
* inaccurate results will be returned.
* @returns Number of samples per frame.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1);
/** Gets the number of channels from an Opus packet.
* @param [in] data <tt>char*</tt>: Opus packet
* @returns Number of channels
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1);
/** Gets the number of frames in an Opus packet.
* @param [in] packet <tt>char*</tt>: Opus packet
* @param [in] len <tt>opus_int32</tt>: Length of packet
* @returns Number of frames
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1);
/** Gets the number of samples of an Opus packet.
* @param [in] packet <tt>char*</tt>: Opus packet
* @param [in] len <tt>opus_int32</tt>: Length of packet
* @param [in] Fs <tt>opus_int32</tt>: Sampling rate in Hz.
* This must be a multiple of 400, or
* inaccurate results will be returned.
* @returns Number of samples
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1);
/** Gets the number of samples of an Opus packet.
* @param [in] dec <tt>OpusDecoder*</tt>: Decoder state
* @param [in] packet <tt>char*</tt>: Opus packet
* @param [in] len <tt>opus_int32</tt>: Length of packet
* @returns Number of samples
* @retval OPUS_BAD_ARG Insufficient data was passed to the function
* @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
/** Applies soft-clipping to bring a float signal within the [-1,1] range. If
* the signal is already in that range, nothing is done. If there are values
* outside of [-1,1], then the signal is clipped as smoothly as possible to
* both fit in the range and avoid creating excessive distortion in the
* process.
* @param [in,out] pcm <tt>float*</tt>: Input PCM and modified PCM
* @param [in] frame_size <tt>int</tt> Number of samples per channel to process
* @param [in] channels <tt>int</tt>: Number of channels
* @param [in,out] softclip_mem <tt>float*</tt>: State memory for the soft clipping process (one float per channel, initialized to zero)
*/
OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem);
/**@}*/
/** @defgroup opus_repacketizer Repacketizer
* @{
*
* The repacketizer can be used to merge multiple Opus packets into a single
* packet or alternatively to split Opus packets that have previously been
* merged. Splitting valid Opus packets is always guaranteed to succeed,
* whereas merging valid packets only succeeds if all frames have the same
* mode, bandwidth, and frame size, and when the total duration of the merged
* packet is no more than 120 ms. The 120 ms limit comes from the
* specification and limits decoder memory requirements at a point where
* framing overhead becomes negligible.
*
* The repacketizer currently only operates on elementary Opus
* streams. It will not manipualte multistream packets successfully, except in
* the degenerate case where they consist of data from a single stream.
*
* The repacketizing process starts with creating a repacketizer state, either
* by calling opus_repacketizer_create() or by allocating the memory yourself,
* e.g.,
* @code
* OpusRepacketizer *rp;
* rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size());
* if (rp != NULL)
* opus_repacketizer_init(rp);
* @endcode
*
* Then the application should submit packets with opus_repacketizer_cat(),
* extract new packets with opus_repacketizer_out() or
* opus_repacketizer_out_range(), and then reset the state for the next set of
* input packets via opus_repacketizer_init().
*
* For example, to split a sequence of packets into individual frames:
* @code
* unsigned char *data;
* int len;
* while (get_next_packet(&data, &len))
* {
* unsigned char out[1276];
* opus_int32 out_len;
* int nb_frames;
* int err;
* int i;
* err = opus_repacketizer_cat(rp, data, len);
* if (err != OPUS_OK)
* {
* release_packet(data);
* return err;
* }
* nb_frames = opus_repacketizer_get_nb_frames(rp);
* for (i = 0; i < nb_frames; i++)
* {
* out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out));
* if (out_len < 0)
* {
* release_packet(data);
* return (int)out_len;
* }
* output_next_packet(out, out_len);
* }
* opus_repacketizer_init(rp);
* release_packet(data);
* }
* @endcode
*
* Alternatively, to combine a sequence of frames into packets that each
* contain up to <code>TARGET_DURATION_MS</code> milliseconds of data:
* @code
* // The maximum number of packets with duration TARGET_DURATION_MS occurs
* // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5)
* // packets.
* unsigned char *data[(TARGET_DURATION_MS*2/5)+1];
* opus_int32 len[(TARGET_DURATION_MS*2/5)+1];
* int nb_packets;
* unsigned char out[1277*(TARGET_DURATION_MS*2/2)];
* opus_int32 out_len;
* int prev_toc;
* nb_packets = 0;
* while (get_next_packet(data+nb_packets, len+nb_packets))
* {
* int nb_frames;
* int err;
* nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]);
* if (nb_frames < 1)
* {
* release_packets(data, nb_packets+1);
* return nb_frames;
* }
* nb_frames += opus_repacketizer_get_nb_frames(rp);
* // If adding the next packet would exceed our target, or it has an
* // incompatible TOC sequence, output the packets we already have before
* // submitting it.
* // N.B., The nb_packets > 0 check ensures we've submitted at least one
* // packet since the last call to opus_repacketizer_init(). Otherwise a
* // single packet longer than TARGET_DURATION_MS would cause us to try to
* // output an (invalid) empty packet. It also ensures that prev_toc has
* // been set to a valid value. Additionally, len[nb_packets] > 0 is
* // guaranteed by the call to opus_packet_get_nb_frames() above, so the
* // reference to data[nb_packets][0] should be valid.
* if (nb_packets > 0 && (
* ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) ||
* opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames >
* TARGET_DURATION_MS*48))
* {
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
* if (out_len < 0)
* {
* release_packets(data, nb_packets+1);
* return (int)out_len;
* }
* output_next_packet(out, out_len);
* opus_repacketizer_init(rp);
* release_packets(data, nb_packets);
* data[0] = data[nb_packets];
* len[0] = len[nb_packets];
* nb_packets = 0;
* }
* err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]);
* if (err != OPUS_OK)
* {
* release_packets(data, nb_packets+1);
* return err;
* }
* prev_toc = data[nb_packets][0];
* nb_packets++;
* }
* // Output the final, partial packet.
* if (nb_packets > 0)
* {
* out_len = opus_repacketizer_out(rp, out, sizeof(out));
* release_packets(data, nb_packets);
* if (out_len < 0)
* return (int)out_len;
* output_next_packet(out, out_len);
* }
* @endcode
*
* An alternate way of merging packets is to simply call opus_repacketizer_cat()
* unconditionally until it fails. At that point, the merged packet can be
* obtained with opus_repacketizer_out() and the input packet for which
* opus_repacketizer_cat() needs to be re-added to a newly reinitialized
* repacketizer state.
*/
typedef struct OpusRepacketizer OpusRepacketizer;
/** Gets the size of an <code>OpusRepacketizer</code> structure.
* @returns The size in bytes.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void);
/** (Re)initializes a previously allocated repacketizer state.
* The state must be at least the size returned by opus_repacketizer_get_size().
* This can be used for applications which use their own allocator instead of
* malloc().
* It must also be called to reset the queue of packets waiting to be
* repacketized, which is necessary if the maximum packet duration of 120 ms
* is reached or if you wish to submit packets with a different Opus
* configuration (coding mode, audio bandwidth, frame size, or channel count).
* Failure to do so will prevent a new packet from being added with
* opus_repacketizer_cat().
* @see opus_repacketizer_create
* @see opus_repacketizer_get_size
* @see opus_repacketizer_cat
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to
* (re)initialize.
* @returns A pointer to the same repacketizer state that was passed in.
*/
OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
/** Allocates memory and initializes the new repacketizer with
* opus_repacketizer_init().
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void);
/** Frees an <code>OpusRepacketizer</code> allocated by
* opus_repacketizer_create().
* @param[in] rp <tt>OpusRepacketizer*</tt>: State to be freed.
*/
OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp);
/** Add a packet to the current repacketizer state.
* This packet must match the configuration of any packets already submitted
* for repacketization since the last call to opus_repacketizer_init().
* This means that it must have the same coding mode, audio bandwidth, frame
* size, and channel count.
* This can be checked in advance by examining the top 6 bits of the first
* byte of the packet, and ensuring they match the top 6 bits of the first
* byte of any previously submitted packet.
* The total duration of audio in the repacketizer state also must not exceed
* 120 ms, the maximum duration of a single packet, after adding this packet.
*
* The contents of the current repacketizer state can be extracted into new
* packets using opus_repacketizer_out() or opus_repacketizer_out_range().
*
* In order to add a packet with a different configuration or to add more
* audio beyond 120 ms, you must clear the repacketizer state by calling
* opus_repacketizer_init().
* If a packet is too large to add to the current repacketizer state, no part
* of it is added, even if it contains multiple frames, some of which might
* fit.
* If you wish to be able to add parts of such packets, you should first use
* another repacketizer to split the packet into pieces and add them
* individually.
* @see opus_repacketizer_out_range
* @see opus_repacketizer_out
* @see opus_repacketizer_init
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state to which to
* add the packet.
* @param[in] data <tt>const unsigned char*</tt>: The packet data.
* The application must ensure
* this pointer remains valid
* until the next call to
* opus_repacketizer_init() or
* opus_repacketizer_destroy().
* @param len <tt>opus_int32</tt>: The number of bytes in the packet data.
* @returns An error code indicating whether or not the operation succeeded.
* @retval #OPUS_OK The packet's contents have been added to the repacketizer
* state.
* @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence,
* the packet's TOC sequence was not compatible
* with previously submitted packets (because
* the coding mode, audio bandwidth, frame size,
* or channel count did not match), or adding
* this packet would increase the total amount of
* audio stored in the repacketizer state to more
* than 120 ms.
*/
OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2);
/** Construct a new packet from data previously submitted to the repacketizer
* state via opus_repacketizer_cat().
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
* construct the new packet.
* @param begin <tt>int</tt>: The index of the first frame in the current
* repacketizer state to include in the output.
* @param end <tt>int</tt>: One past the index of the last frame in the
* current repacketizer state to include in the
* output.
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
* store the output packet.
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
* the output buffer. In order to guarantee
* success, this should be at least
* <code>1276</code> for a single frame,
* or for multiple frames,
* <code>1277*(end-begin)</code>.
* However, <code>1*(end-begin)</code> plus
* the size of all packet data submitted to
* the repacketizer since the last call to
* opus_repacketizer_init() or
* opus_repacketizer_create() is also
* sufficient, and possibly much smaller.
* @returns The total size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BAD_ARG <code>[begin,end)</code> was an invalid range of
* frames (begin < 0, begin >= end, or end >
* opus_repacketizer_get_nb_frames()).
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
* complete output packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Return the total number of frames contained in packet data submitted to
* the repacketizer state so far via opus_repacketizer_cat() since the last
* call to opus_repacketizer_init() or opus_repacketizer_create().
* This defines the valid range of packets that can be extracted with
* opus_repacketizer_out_range() or opus_repacketizer_out().
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state containing the
* frames.
* @returns The total number of frames contained in the packet data submitted
* to the repacketizer state.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1);
/** Construct a new packet from data previously submitted to the repacketizer
* state via opus_repacketizer_cat().
* This is a convenience routine that returns all the data submitted so far
* in a single packet.
* It is equivalent to calling
* @code
* opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp),
* data, maxlen)
* @endcode
* @param rp <tt>OpusRepacketizer*</tt>: The repacketizer state from which to
* construct the new packet.
* @param[out] data <tt>const unsigned char*</tt>: The buffer in which to
* store the output packet.
* @param maxlen <tt>opus_int32</tt>: The maximum number of bytes to store in
* the output buffer. In order to guarantee
* success, this should be at least
* <code>1277*opus_repacketizer_get_nb_frames(rp)</code>.
* However,
* <code>1*opus_repacketizer_get_nb_frames(rp)</code>
* plus the size of all packet data
* submitted to the repacketizer since the
* last call to opus_repacketizer_init() or
* opus_repacketizer_create() is also
* sufficient, and possibly much smaller.
* @returns The total size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the
* complete output packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1);
/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence).
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to pad.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
* This must be at least as large as len.
* @returns an error code
* @retval #OPUS_OK \a on success.
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len);
/** Remove all padding from a given Opus packet and rewrite the TOC sequence to
* minimize space usage.
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to strip.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @returns The new size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BAD_ARG \a len was less than 1.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len);
/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence).
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to pad.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @param new_len <tt>opus_int32</tt>: The desired size of the packet after padding.
* This must be at least 1.
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
* This must be at least as large as len.
* @returns an error code
* @retval #OPUS_OK \a on success.
* @retval #OPUS_BAD_ARG \a len was less than 1.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams);
/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to
* minimize space usage.
* @param[in,out] data <tt>const unsigned char*</tt>: The buffer containing the
* packet to strip.
* @param len <tt>opus_int32</tt>: The size of the packet.
* This must be at least 1.
* @param nb_streams <tt>opus_int32</tt>: The number of streams (not channels) in the packet.
* This must be at least 1.
* @returns The new size of the output packet on success, or an error code
* on failure.
* @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len.
* @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams);
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* OPUS_H */

753
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_defines.h

@ -0,0 +1,753 @@
/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited
Written by Jean-Marc Valin and Koen Vos */
/*
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.
*/
/**
* @file opus_defines.h
* @brief Opus reference implementation constants
*/
#ifndef OPUS_DEFINES_H
#define OPUS_DEFINES_H
#include "opus_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @defgroup opus_errorcodes Error codes
* @{
*/
/** No error @hideinitializer*/
#define OPUS_OK 0
/** One or more invalid/out of range arguments @hideinitializer*/
#define OPUS_BAD_ARG -1
/** Not enough bytes allocated in the buffer @hideinitializer*/
#define OPUS_BUFFER_TOO_SMALL -2
/** An internal error was detected @hideinitializer*/
#define OPUS_INTERNAL_ERROR -3
/** The compressed data passed is corrupted @hideinitializer*/
#define OPUS_INVALID_PACKET -4
/** Invalid/unsupported request number @hideinitializer*/
#define OPUS_UNIMPLEMENTED -5
/** An encoder or decoder structure is invalid or already freed @hideinitializer*/
#define OPUS_INVALID_STATE -6
/** Memory allocation has failed @hideinitializer*/
#define OPUS_ALLOC_FAIL -7
/**@}*/
/** @cond OPUS_INTERNAL_DOC */
/**Export control for opus functions */
#ifndef OPUS_EXPORT
# if defined(WIN32)
# if defined(OPUS_BUILD) && defined(DLL_EXPORT)
# define OPUS_EXPORT __declspec(dllexport)
# else
# define OPUS_EXPORT
# endif
# elif defined(__GNUC__) && defined(OPUS_BUILD)
# define OPUS_EXPORT __attribute__ ((visibility ("default")))
# else
# define OPUS_EXPORT
# endif
#endif
# if !defined(OPUS_GNUC_PREREQ)
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
# define OPUS_GNUC_PREREQ(_maj,_min) \
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
# else
# define OPUS_GNUC_PREREQ(_maj,_min) 0
# endif
# endif
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
# if OPUS_GNUC_PREREQ(3,0)
# define OPUS_RESTRICT __restrict__
# elif (defined(_MSC_VER) && _MSC_VER >= 1400)
# define OPUS_RESTRICT __restrict
# else
# define OPUS_RESTRICT
# endif
#else
# define OPUS_RESTRICT restrict
#endif
#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) )
# if OPUS_GNUC_PREREQ(2,7)
# define OPUS_INLINE __inline__
# elif (defined(_MSC_VER))
# define OPUS_INLINE __inline
# else
# define OPUS_INLINE
# endif
#else
# define OPUS_INLINE inline
#endif
/**Warning attributes for opus functions
* NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out
* some paranoid null checks. */
#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__))
#else
# define OPUS_WARN_UNUSED_RESULT
#endif
#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4)
# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x)))
#else
# define OPUS_ARG_NONNULL(_x)
#endif
/** These are the actual Encoder CTL ID numbers.
* They should not be used directly by applications.
* In general, SETs should be even and GETs should be odd.*/
#define OPUS_SET_APPLICATION_REQUEST 4000
#define OPUS_GET_APPLICATION_REQUEST 4001
#define OPUS_SET_BITRATE_REQUEST 4002
#define OPUS_GET_BITRATE_REQUEST 4003
#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004
#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005
#define OPUS_SET_VBR_REQUEST 4006
#define OPUS_GET_VBR_REQUEST 4007
#define OPUS_SET_BANDWIDTH_REQUEST 4008
#define OPUS_GET_BANDWIDTH_REQUEST 4009
#define OPUS_SET_COMPLEXITY_REQUEST 4010
#define OPUS_GET_COMPLEXITY_REQUEST 4011
#define OPUS_SET_INBAND_FEC_REQUEST 4012
#define OPUS_GET_INBAND_FEC_REQUEST 4013
#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014
#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015
#define OPUS_SET_DTX_REQUEST 4016
#define OPUS_GET_DTX_REQUEST 4017
#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020
#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021
#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022
#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023
#define OPUS_SET_SIGNAL_REQUEST 4024
#define OPUS_GET_SIGNAL_REQUEST 4025
#define OPUS_GET_LOOKAHEAD_REQUEST 4027
/* #define OPUS_RESET_STATE 4028 */
#define OPUS_GET_SAMPLE_RATE_REQUEST 4029
#define OPUS_GET_FINAL_RANGE_REQUEST 4031
#define OPUS_GET_PITCH_REQUEST 4033
#define OPUS_SET_GAIN_REQUEST 4034
#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */
#define OPUS_SET_LSB_DEPTH_REQUEST 4036
#define OPUS_GET_LSB_DEPTH_REQUEST 4037
#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039
#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040
#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041
#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042
#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043
/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */
/* Macros to trigger compilation errors when the wrong types are provided to a CTL */
#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x))
#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr)))
#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr)))
#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr)))
/** @endcond */
/** @defgroup opus_ctlvalues Pre-defined values for CTL interface
* @see opus_genericctls, opus_encoderctls
* @{
*/
/* Values for the various encoder CTLs */
#define OPUS_AUTO -1000 /**<Auto/default setting @hideinitializer*/
#define OPUS_BITRATE_MAX -1 /**<Maximum bitrate @hideinitializer*/
/** Best for most VoIP/videoconference applications where listening quality and intelligibility matter most
* @hideinitializer */
#define OPUS_APPLICATION_VOIP 2048
/** Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to the input
* @hideinitializer */
#define OPUS_APPLICATION_AUDIO 2049
/** Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
* @hideinitializer */
#define OPUS_APPLICATION_RESTRICTED_LOWDELAY 2051
#define OPUS_SIGNAL_VOICE 3001 /**< Signal being encoded is voice */
#define OPUS_SIGNAL_MUSIC 3002 /**< Signal being encoded is music */
#define OPUS_BANDWIDTH_NARROWBAND 1101 /**< 4 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_MEDIUMBAND 1102 /**< 6 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_WIDEBAND 1103 /**< 8 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_SUPERWIDEBAND 1104 /**<12 kHz bandpass @hideinitializer*/
#define OPUS_BANDWIDTH_FULLBAND 1105 /**<20 kHz bandpass @hideinitializer*/
#define OPUS_FRAMESIZE_ARG 5000 /**< Select frame size from the argument (default) */
#define OPUS_FRAMESIZE_2_5_MS 5001 /**< Use 2.5 ms frames */
#define OPUS_FRAMESIZE_5_MS 5002 /**< Use 5 ms frames */
#define OPUS_FRAMESIZE_10_MS 5003 /**< Use 10 ms frames */
#define OPUS_FRAMESIZE_20_MS 5004 /**< Use 20 ms frames */
#define OPUS_FRAMESIZE_40_MS 5005 /**< Use 40 ms frames */
#define OPUS_FRAMESIZE_60_MS 5006 /**< Use 60 ms frames */
/**@}*/
/** @defgroup opus_encoderctls Encoder related CTLs
*
* These are convenience macros for use with the \c opus_encode_ctl
* interface. They are used to generate the appropriate series of
* arguments for that call, passing the correct type, size and so
* on as expected for each particular request.
*
* Some usage examples:
*
* @code
* int ret;
* ret = opus_encoder_ctl(enc_ctx, OPUS_SET_BANDWIDTH(OPUS_AUTO));
* if (ret != OPUS_OK) return ret;
*
* opus_int32 rate;
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&rate));
*
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
* @endcode
*
* @see opus_genericctls, opus_encoder
* @{
*/
/** Configures the encoder's computational complexity.
* The supported range is 0-10 inclusive with 10 representing the highest complexity.
* @see OPUS_GET_COMPLEXITY
* @param[in] x <tt>opus_int32</tt>: Allowed values: 0-10, inclusive.
*
* @hideinitializer */
#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x)
/** Gets the encoder's complexity configuration.
* @see OPUS_SET_COMPLEXITY
* @param[out] x <tt>opus_int32 *</tt>: Returns a value in the range 0-10,
* inclusive.
* @hideinitializer */
#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x)
/** Configures the bitrate in the encoder.
* Rates from 500 to 512000 bits per second are meaningful, as well as the
* special values #OPUS_AUTO and #OPUS_BITRATE_MAX.
* The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much
* rate as it can, which is useful for controlling the rate by adjusting the
* output buffer size.
* @see OPUS_GET_BITRATE
* @param[in] x <tt>opus_int32</tt>: Bitrate in bits per second. The default
* is determined based on the number of
* channels and the input sampling rate.
* @hideinitializer */
#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x)
/** Gets the encoder's bitrate configuration.
* @see OPUS_SET_BITRATE
* @param[out] x <tt>opus_int32 *</tt>: Returns the bitrate in bits per second.
* The default is determined based on the
* number of channels and the input
* sampling rate.
* @hideinitializer */
#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x)
/** Enables or disables variable bitrate (VBR) in the encoder.
* The configured bitrate may not be met exactly because frames must
* be an integer number of bytes in length.
* @see OPUS_GET_VBR
* @see OPUS_SET_VBR_CONSTRAINT
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Hard CBR. For LPC/hybrid modes at very low bit-rate, this can
* cause noticeable quality degradation.</dd>
* <dt>1</dt><dd>VBR (default). The exact type of VBR is controlled by
* #OPUS_SET_VBR_CONSTRAINT.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x)
/** Determine if variable bitrate (VBR) is enabled in the encoder.
* @see OPUS_SET_VBR
* @see OPUS_GET_VBR_CONSTRAINT
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Hard CBR.</dd>
* <dt>1</dt><dd>VBR (default). The exact type of VBR may be retrieved via
* #OPUS_GET_VBR_CONSTRAINT.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x)
/** Enables or disables constrained VBR in the encoder.
* This setting is ignored when the encoder is in CBR mode.
* @warning Only the MDCT mode of Opus currently heeds the constraint.
* Speech mode ignores it completely, hybrid mode may fail to obey it
* if the LPC layer uses more bitrate than the constraint would have
* permitted.
* @see OPUS_GET_VBR_CONSTRAINT
* @see OPUS_SET_VBR
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Unconstrained VBR.</dd>
* <dt>1</dt><dd>Constrained VBR (default). This creates a maximum of one
* frame of buffering delay assuming a transport with a
* serialization speed of the nominal bitrate.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x)
/** Determine if constrained VBR is enabled in the encoder.
* @see OPUS_SET_VBR_CONSTRAINT
* @see OPUS_GET_VBR
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Unconstrained VBR.</dd>
* <dt>1</dt><dd>Constrained VBR (default).</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x)
/** Configures mono/stereo forcing in the encoder.
* This can force the encoder to produce packets encoded as either mono or
* stereo, regardless of the format of the input audio. This is useful when
* the caller knows that the input signal is currently a mono source embedded
* in a stereo stream.
* @see OPUS_GET_FORCE_CHANNELS
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
* <dt>1</dt> <dd>Forced mono</dd>
* <dt>2</dt> <dd>Forced stereo</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x)
/** Gets the encoder's forced channel configuration.
* @see OPUS_SET_FORCE_CHANNELS
* @param[out] x <tt>opus_int32 *</tt>:
* <dl>
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
* <dt>1</dt> <dd>Forced mono</dd>
* <dt>2</dt> <dd>Forced stereo</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x)
/** Configures the maximum bandpass that the encoder will select automatically.
* Applications should normally use this instead of #OPUS_SET_BANDWIDTH
* (leaving that set to the default, #OPUS_AUTO). This allows the
* application to set an upper bound based on the type of input it is
* providing, but still gives the encoder the freedom to reduce the bandpass
* when the bitrate becomes too low, for better overall quality.
* @see OPUS_GET_MAX_BANDWIDTH
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured maximum allowed bandpass.
* @see OPUS_SET_MAX_BANDWIDTH
* @param[out] x <tt>opus_int32 *</tt>: Allowed values:
* <dl>
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
/** Sets the encoder's bandpass to a specific value.
* This prevents the encoder from automatically selecting the bandpass based
* on the available bitrate. If an application knows the bandpass of the input
* audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH
* instead, which still gives the encoder the freedom to reduce the bandpass
* when the bitrate becomes too low, for better overall quality.
* @see OPUS_GET_BANDWIDTH
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x)
/** Configures the type of signal being encoded.
* This is a hint which helps the encoder's mode selection.
* @see OPUS_GET_SIGNAL
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured signal type.
* @see OPUS_SET_SIGNAL
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's intended application.
* The initial value is a mandatory argument to the encoder_create function.
* @see OPUS_GET_APPLICATION
* @param[in] x <tt>opus_int32</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured application.
* @see OPUS_SET_APPLICATION
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x)
/** Gets the total samples of delay added by the entire codec.
* This can be queried by the encoder and then the provided number of samples can be
* skipped on from the start of the decoder's output to provide time aligned input
* and output. From the perspective of a decoding application the real data begins this many
* samples late.
*
* The decoder contribution to this delay is identical for all decoders, but the
* encoder portion of the delay may vary from implementation to implementation,
* version to version, or even depend on the encoder's initial configuration.
* Applications needing delay compensation should call this CTL rather than
* hard-coding a value.
* @param[out] x <tt>opus_int32 *</tt>: Number of lookahead samples
* @hideinitializer */
#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's use of inband forward error correction (FEC).
* @note This is only applicable to the LPC layer
* @see OPUS_GET_INBAND_FEC
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Disable inband FEC (default).</dd>
* <dt>1</dt><dd>Enable inband FEC.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x)
/** Gets encoder's configured use of inband forward error correction.
* @see OPUS_SET_INBAND_FEC
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Inband FEC disabled (default).</dd>
* <dt>1</dt><dd>Inband FEC enabled.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's expected packet loss percentage.
* Higher values trigger progressively more loss resistant behavior in the encoder
* at the expense of quality at a given bitrate in the absence of packet loss, but
* greater quality under loss.
* @see OPUS_GET_PACKET_LOSS_PERC
* @param[in] x <tt>opus_int32</tt>: Loss percentage in the range 0-100, inclusive (default: 0).
* @hideinitializer */
#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured packet loss percentage.
* @see OPUS_SET_PACKET_LOSS_PERC
* @param[out] x <tt>opus_int32 *</tt>: Returns the configured loss percentage
* in the range 0-100, inclusive (default: 0).
* @hideinitializer */
#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's use of discontinuous transmission (DTX).
* @note This is only applicable to the LPC layer
* @see OPUS_GET_DTX
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Disable DTX (default).</dd>
* <dt>1</dt><dd>Enabled DTX.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x)
/** Gets encoder's configured use of discontinuous transmission.
* @see OPUS_SET_DTX
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>DTX disabled (default).</dd>
* <dt>1</dt><dd>DTX enabled.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x)
/** Configures the depth of signal being encoded.
*
* This is a hint which helps the encoder identify silence and near-silence.
* It represents the number of significant bits of linear intensity below
* which the signal contains ignorable quantization or other noise.
*
* For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting
* for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate
* for 16-bit linear pcm input with opus_encode_float().
*
* When using opus_encode() instead of opus_encode_float(), or when libopus
* is compiled for fixed-point, the encoder uses the minimum of the value
* set here and the value 16.
*
* @see OPUS_GET_LSB_DEPTH
* @param[in] x <tt>opus_int32</tt>: Input precision in bits, between 8 and 24
* (default: 24).
* @hideinitializer */
#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured signal depth.
* @see OPUS_SET_LSB_DEPTH
* @param[out] x <tt>opus_int32 *</tt>: Input precision in bits, between 8 and
* 24 (default: 24).
* @hideinitializer */
#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x)
/** Configures the encoder's use of variable duration frames.
* When variable duration is enabled, the encoder is free to use a shorter frame
* size than the one requested in the opus_encode*() call.
* It is then the user's responsibility
* to verify how much audio was encoded by checking the ToC byte of the encoded
* packet. The part of the audio that was not encoded needs to be resent to the
* encoder for the next call. Do not use this option unless you <b>really</b>
* know what you are doing.
* @see OPUS_GET_EXPERT_FRAME_DURATION
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_VARIABLE</dt><dd>Optimize the frame size dynamically.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured use of variable duration frames.
* @see OPUS_SET_EXPERT_FRAME_DURATION
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
* <dt>OPUS_FRAMESIZE_VARIABLE</dt><dd>Optimize the frame size dynamically.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x)
/** If set to 1, disables almost all use of prediction, making frames almost
* completely independent. This reduces quality.
* @see OPUS_GET_PREDICTION_DISABLED
* @param[in] x <tt>opus_int32</tt>: Allowed values:
* <dl>
* <dt>0</dt><dd>Enable prediction (default).</dd>
* <dt>1</dt><dd>Disable prediction.</dd>
* </dl>
* @hideinitializer */
#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x)
/** Gets the encoder's configured prediction status.
* @see OPUS_SET_PREDICTION_DISABLED
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>0</dt><dd>Prediction enabled (default).</dd>
* <dt>1</dt><dd>Prediction disabled.</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x)
/**@}*/
/** @defgroup opus_genericctls Generic CTLs
*
* These macros are used with the \c opus_decoder_ctl and
* \c opus_encoder_ctl calls to generate a particular
* request.
*
* When called on an \c OpusDecoder they apply to that
* particular decoder instance. When called on an
* \c OpusEncoder they apply to the corresponding setting
* on that encoder instance, if present.
*
* Some usage examples:
*
* @code
* int ret;
* opus_int32 pitch;
* ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch));
* if (ret == OPUS_OK) return ret;
*
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
* opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE);
*
* opus_int32 enc_bw, dec_bw;
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw));
* opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw));
* if (enc_bw != dec_bw) {
* printf("packet bandwidth mismatch!\n");
* }
* @endcode
*
* @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls
* @{
*/
/** Resets the codec state to be equivalent to a freshly initialized state.
* This should be called when switching streams in order to prevent
* the back to back decoding from giving different results from
* one at a time decoding.
* @hideinitializer */
#define OPUS_RESET_STATE 4028
/** Gets the final state of the codec's entropy coder.
* This is used for testing purposes,
* The encoder and decoder state should be identical after coding a payload
* (assuming no data corruption or software bugs)
*
* @param[out] x <tt>opus_uint32 *</tt>: Entropy coder state
*
* @hideinitializer */
#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x)
/** Gets the encoder's configured bandpass or the decoder's last bandpass.
* @see OPUS_SET_BANDWIDTH
* @param[out] x <tt>opus_int32 *</tt>: Returns one of the following values:
* <dl>
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
* </dl>
* @hideinitializer */
#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x)
/** Gets the sampling rate the encoder or decoder was initialized with.
* This simply returns the <code>Fs</code> value passed to opus_encoder_init()
* or opus_decoder_init().
* @param[out] x <tt>opus_int32 *</tt>: Sampling rate of encoder or decoder.
* @hideinitializer
*/
#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x)
/**@}*/
/** @defgroup opus_decoderctls Decoder related CTLs
* @see opus_genericctls, opus_encoderctls, opus_decoder
* @{
*/
/** Configures decoder gain adjustment.
* Scales the decoded output by a factor specified in Q8 dB units.
* This has a maximum range of -32768 to 32767 inclusive, and returns
* OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment.
* This setting survives decoder reset.
*
* gain = pow(10, x/(20.0*256))
*
* @param[in] x <tt>opus_int32</tt>: Amount to scale PCM signal by in Q8 dB units.
* @hideinitializer */
#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x)
/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN
*
* @param[out] x <tt>opus_int32 *</tt>: Amount to scale PCM signal by in Q8 dB units.
* @hideinitializer */
#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x)
/** Gets the duration (in samples) of the last packet successfully decoded or concealed.
* @param[out] x <tt>opus_int32 *</tt>: Number of samples (at current sampling rate).
* @hideinitializer */
#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x)
/** Gets the pitch of the last decoded frame, if available.
* This can be used for any post-processing algorithm requiring the use of pitch,
* e.g. time stretching/shortening. If the last frame was not voiced, or if the
* pitch was not coded in the frame, then zero is returned.
*
* This CTL is only implemented for decoder instances.
*
* @param[out] x <tt>opus_int32 *</tt>: pitch period at 48 kHz (or 0 if not available)
*
* @hideinitializer */
#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x)
/**@}*/
/** @defgroup opus_libinfo Opus library information functions
* @{
*/
/** Converts an opus error code into a human readable string.
*
* @param[in] error <tt>int</tt>: Error number
* @returns Error string
*/
OPUS_EXPORT const char *opus_strerror(int error);
/** Gets the libopus version string.
*
* Applications may look for the substring "-fixed" in the version string to
* determine whether they have a fixed-point or floating-point build at
* runtime.
*
* @returns Version string
*/
OPUS_EXPORT const char *opus_get_version_string(void);
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* OPUS_DEFINES_H */

660
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_multistream.h

@ -0,0 +1,660 @@
/* Copyright (c) 2011 Xiph.Org Foundation
Written by Jean-Marc Valin */
/*
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.
*/
/**
* @file opus_multistream.h
* @brief Opus reference implementation multistream API
*/
#ifndef OPUS_MULTISTREAM_H
#define OPUS_MULTISTREAM_H
#include "opus.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @cond OPUS_INTERNAL_DOC */
/** Macros to trigger compilation errors when the wrong types are provided to a
* CTL. */
/**@{*/
#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr)))
#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr)))
/**@}*/
/** These are the actual encoder and decoder CTL ID numbers.
* They should not be used directly by applications.
* In general, SETs should be even and GETs should be odd.*/
/**@{*/
#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120
#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122
/**@}*/
/** @endcond */
/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs
*
* These are convenience macros that are specific to the
* opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl()
* interface.
* The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and
* @ref opus_decoderctls may be applied to a multistream encoder or decoder as
* well.
* In addition, you may retrieve the encoder or decoder state for an specific
* stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or
* #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually.
*/
/**@{*/
/** Gets the encoder state for an individual stream of a multistream encoder.
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose encoder you
* wish to retrieve.
* This must be non-negative and less than
* the <code>streams</code> parameter used
* to initialize the encoder.
* @param[out] y <tt>OpusEncoder**</tt>: Returns a pointer to the given
* encoder state.
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
* @hideinitializer
*/
#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y)
/** Gets the decoder state for an individual stream of a multistream decoder.
* @param[in] x <tt>opus_int32</tt>: The index of the stream whose decoder you
* wish to retrieve.
* This must be non-negative and less than
* the <code>streams</code> parameter used
* to initialize the decoder.
* @param[out] y <tt>OpusDecoder**</tt>: Returns a pointer to the given
* decoder state.
* @retval OPUS_BAD_ARG The index of the requested stream was out of range.
* @hideinitializer
*/
#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y)
/**@}*/
/** @defgroup opus_multistream Opus Multistream API
* @{
*
* The multistream API allows individual Opus streams to be combined into a
* single packet, enabling support for up to 255 channels. Unlike an
* elementary Opus stream, the encoder and decoder must negotiate the channel
* configuration before the decoder can successfully interpret the data in the
* packets produced by the encoder. Some basic information, such as packet
* duration, can be computed without any special negotiation.
*
* The format for multistream Opus packets is defined in
* <a href="https://tools.ietf.org/html/rfc7845">RFC 7845</a>
* and is based on the self-delimited Opus framing described in Appendix B of
* <a href="https://tools.ietf.org/html/rfc6716">RFC 6716</a>.
* Normal Opus packets are just a degenerate case of multistream Opus packets,
* and can be encoded or decoded with the multistream API by setting
* <code>streams</code> to <code>1</code> when initializing the encoder or
* decoder.
*
* Multistream Opus streams can contain up to 255 elementary Opus streams.
* These may be either "uncoupled" or "coupled", indicating that the decoder
* is configured to decode them to either 1 or 2 channels, respectively.
* The streams are ordered so that all coupled streams appear at the
* beginning.
*
* A <code>mapping</code> table defines which decoded channel <code>i</code>
* should be used for each input/output (I/O) channel <code>j</code>. This table is
* typically provided as an unsigned char array.
* Let <code>i = mapping[j]</code> be the index for I/O channel <code>j</code>.
* If <code>i < 2*coupled_streams</code>, then I/O channel <code>j</code> is
* encoded as the left channel of stream <code>(i/2)</code> if <code>i</code>
* is even, or as the right channel of stream <code>(i/2)</code> if
* <code>i</code> is odd. Otherwise, I/O channel <code>j</code> is encoded as
* mono in stream <code>(i - coupled_streams)</code>, unless it has the special
* value 255, in which case it is omitted from the encoding entirely (the
* decoder will reproduce it as silence). Each value <code>i</code> must either
* be the special value 255 or be less than <code>streams + coupled_streams</code>.
*
* The output channels specified by the encoder
* should use the
* <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
* channel ordering</a>. A decoder may wish to apply an additional permutation
* to the mapping the encoder used to achieve a different output channel
* order (e.g. for outputing in WAV order).
*
* Each multistream packet contains an Opus packet for each stream, and all of
* the Opus packets in a single multistream packet must have the same
* duration. Therefore the duration of a multistream packet can be extracted
* from the TOC sequence of the first stream, which is located at the
* beginning of the packet, just like an elementary Opus stream:
*
* @code
* int nb_samples;
* int nb_frames;
* nb_frames = opus_packet_get_nb_frames(data, len);
* if (nb_frames < 1)
* return nb_frames;
* nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames;
* @endcode
*
* The general encoding and decoding process proceeds exactly the same as in
* the normal @ref opus_encoder and @ref opus_decoder APIs.
* See their documentation for an overview of how to use the corresponding
* multistream functions.
*/
/** Opus multistream encoder state.
* This contains the complete state of a multistream Opus encoder.
* It is position independent and can be freely copied.
* @see opus_multistream_encoder_create
* @see opus_multistream_encoder_init
*/
typedef struct OpusMSEncoder OpusMSEncoder;
/** Opus multistream decoder state.
* This contains the complete state of a multistream Opus decoder.
* It is position independent and can be freely copied.
* @see opus_multistream_decoder_create
* @see opus_multistream_decoder_init
*/
typedef struct OpusMSDecoder OpusMSDecoder;
/**\name Multistream encoder functions */
/**@{*/
/** Gets the size of an OpusMSEncoder structure.
* @param streams <tt>int</tt>: The total number of streams to encode from the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
* to encode.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* encoded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @returns The size in bytes on success, or a negative error code
* (see @ref opus_errorcodes) on error.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size(
int streams,
int coupled_streams
);
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size(
int channels,
int mapping_family
);
/** Allocates and initializes a multistream encoder state.
* Call opus_multistream_encoder_destroy() to release
* this object when finished.
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels in the input signal.
* This must be at most 255.
* It may be greater than the number of
* coded channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams to encode from the
* input.
* This must be no more than the number of channels.
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
* to encode.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* encoded channels (<code>streams +
* coupled_streams</code>) must be no
* more than the number of input channels.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* encoded channels to input channels, as described in
* @ref opus_multistream. As an extra constraint, the
* multistream encoder does not allow encoding coupled
* streams for which one channel is unused since this
* is never a good idea.
* @param application <tt>int</tt>: The target encoder application.
* This must be one of the following:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
* code (see @ref opus_errorcodes) on
* failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create(
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping,
int application,
int *error
) OPUS_ARG_NONNULL(5);
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create(
opus_int32 Fs,
int channels,
int mapping_family,
int *streams,
int *coupled_streams,
unsigned char *mapping,
int application,
int *error
) OPUS_ARG_NONNULL(5);
/** Initialize a previously allocated multistream encoder state.
* The memory pointed to by \a st must be at least the size returned by
* opus_multistream_encoder_get_size().
* This is intended for applications which use their own allocator instead of
* malloc.
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @see opus_multistream_encoder_create
* @see opus_multistream_encoder_get_size
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels in the input signal.
* This must be at most 255.
* It may be greater than the number of
* coded channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams to encode from the
* input.
* This must be no more than the number of channels.
* @param coupled_streams <tt>int</tt>: Number of coupled (2 channel) streams
* to encode.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* encoded channels (<code>streams +
* coupled_streams</code>) must be no
* more than the number of input channels.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* encoded channels to input channels, as described in
* @ref opus_multistream. As an extra constraint, the
* multistream encoder does not allow encoding coupled
* streams for which one channel is unused since this
* is never a good idea.
* @param application <tt>int</tt>: The target encoder application.
* This must be one of the following:
* <dl>
* <dt>#OPUS_APPLICATION_VOIP</dt>
* <dd>Process signal for improved speech intelligibility.</dd>
* <dt>#OPUS_APPLICATION_AUDIO</dt>
* <dd>Favor faithfulness to the original input.</dd>
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
* <dd>Configure the minimum possible coding delay by disabling certain modes
* of operation.</dd>
* </dl>
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
* on failure.
*/
OPUS_EXPORT int opus_multistream_encoder_init(
OpusMSEncoder *st,
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping,
int application
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
OPUS_EXPORT int opus_multistream_surround_encoder_init(
OpusMSEncoder *st,
opus_int32 Fs,
int channels,
int mapping_family,
int *streams,
int *coupled_streams,
unsigned char *mapping,
int application
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
/** Encodes a multistream Opus frame.
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
* @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
* samples.
* This must contain
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
* signal.
* This must be an Opus frame size for the
* encoder's sampling rate.
* For example, at 48 kHz the permitted values
* are 120, 240, 480, 960, 1920, and 2880.
* Passing in a duration of less than 10 ms
* (480 samples at 48 kHz) will prevent the
* encoder from using the LPC or hybrid modes.
* @param[out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode(
OpusMSEncoder *st,
const opus_int16 *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Encodes a multistream Opus frame from floating point input.
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
* @param[in] pcm <tt>const float*</tt>: The input signal as interleaved
* samples with a normal range of
* +/-1.0.
* Samples with a range beyond +/-1.0
* are supported but will be clipped by
* decoders using the integer API and
* should only be used if it is known
* that the far end supports extended
* dynamic range.
* This must contain
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: Number of samples per channel in the input
* signal.
* This must be an Opus frame size for the
* encoder's sampling rate.
* For example, at 48 kHz the permitted values
* are 120, 240, 480, 960, 1920, and 2880.
* Passing in a duration of less than 10 ms
* (480 samples at 48 kHz) will prevent the
* encoder from using the LPC or hybrid modes.
* @param[out] data <tt>unsigned char*</tt>: Output payload.
* This must contain storage for at
* least \a max_data_bytes.
* @param [in] max_data_bytes <tt>opus_int32</tt>: Size of the allocated
* memory for the output
* payload. This may be
* used to impose an upper limit on
* the instant bitrate, but should
* not be used as the only bitrate
* control. Use #OPUS_SET_BITRATE to
* control the bitrate.
* @returns The length of the encoded packet (in bytes) on success or a
* negative error code (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float(
OpusMSEncoder *st,
const float *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4);
/** Frees an <code>OpusMSEncoder</code> allocated by
* opus_multistream_encoder_create().
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to be freed.
*/
OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st);
/** Perform a CTL function on a multistream Opus encoder.
*
* Generally the request and subsequent arguments are generated by a
* convenience macro.
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls,
* @ref opus_encoderctls, or @ref opus_multistream_ctls.
* @see opus_genericctls
* @see opus_encoderctls
* @see opus_multistream_ctls
*/
OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/**@}*/
/**\name Multistream decoder functions */
/**@{*/
/** Gets the size of an <code>OpusMSDecoder</code> structure.
* @param streams <tt>int</tt>: The total number of streams coded in the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number streams to decode as coupled
* (2 channel) streams.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* coded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @returns The size in bytes on success, or a negative error code
* (see @ref opus_errorcodes) on error.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size(
int streams,
int coupled_streams
);
/** Allocates and initializes a multistream decoder state.
* Call opus_multistream_decoder_destroy() to release
* this object when finished.
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels to output.
* This must be at most 255.
* It may be different from the number of coded
* channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams coded in the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
* (2 channel) streams.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* coded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* coded channels to output channels, as described in
* @ref opus_multistream.
* @param[out] error <tt>int *</tt>: Returns #OPUS_OK on success, or an error
* code (see @ref opus_errorcodes) on
* failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create(
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping,
int *error
) OPUS_ARG_NONNULL(5);
/** Intialize a previously allocated decoder state object.
* The memory pointed to by \a st must be at least the size returned by
* opus_multistream_encoder_get_size().
* This is intended for applications which use their own allocator instead of
* malloc.
* To reset a previously initialized state, use the #OPUS_RESET_STATE CTL.
* @see opus_multistream_decoder_create
* @see opus_multistream_deocder_get_size
* @param st <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
* This must be one of 8000, 12000, 16000,
* 24000, or 48000.
* @param channels <tt>int</tt>: Number of channels to output.
* This must be at most 255.
* It may be different from the number of coded
* channels (<code>streams +
* coupled_streams</code>).
* @param streams <tt>int</tt>: The total number of streams coded in the
* input.
* This must be no more than 255.
* @param coupled_streams <tt>int</tt>: Number of streams to decode as coupled
* (2 channel) streams.
* This must be no larger than the total
* number of streams.
* Additionally, The total number of
* coded channels (<code>streams +
* coupled_streams</code>) must be no
* more than 255.
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
* coded channels to output channels, as described in
* @ref opus_multistream.
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes)
* on failure.
*/
OPUS_EXPORT int opus_multistream_decoder_init(
OpusMSDecoder *st,
opus_int32 Fs,
int channels,
int streams,
int coupled_streams,
const unsigned char *mapping
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6);
/** Decode a multistream Opus packet.
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
* Use a <code>NULL</code>
* pointer to indicate packet
* loss.
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
* samples.
* This must contain room for
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: The number of samples per channel of
* available space in \a pcm.
* If this is less than the maximum packet duration
* (120 ms; 5760 for 48kHz), this function will not be capable
* of decoding some packets. In the case of PLC (data==NULL)
* or FEC (decode_fec=1), then frame_size needs to be exactly
* the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the
* next incoming packet. For the PLC and FEC cases, frame_size
* <b>must</b> be a multiple of 2.5 ms.
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
* forward error correction data be decoded.
* If no such data is available, the frame is
* decoded as if it were lost.
* @returns Number of samples decoded on success or a negative error code
* (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode(
OpusMSDecoder *st,
const unsigned char *data,
opus_int32 len,
opus_int16 *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Decode a multistream Opus packet with floating point output.
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
* Use a <code>NULL</code>
* pointer to indicate packet
* loss.
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
* samples.
* This must contain room for
* <code>frame_size*channels</code>
* samples.
* @param frame_size <tt>int</tt>: The number of samples per channel of
* available space in \a pcm.
* If this is less than the maximum packet duration
* (120 ms; 5760 for 48kHz), this function will not be capable
* of decoding some packets. In the case of PLC (data==NULL)
* or FEC (decode_fec=1), then frame_size needs to be exactly
* the duration of audio that is missing, otherwise the
* decoder will not be in the optimal state to decode the
* next incoming packet. For the PLC and FEC cases, frame_size
* <b>must</b> be a multiple of 2.5 ms.
* @param decode_fec <tt>int</tt>: Flag (0 or 1) to request that any in-band
* forward error correction data be decoded.
* If no such data is available, the frame is
* decoded as if it were lost.
* @returns Number of samples decoded on success or a negative error code
* (see @ref opus_errorcodes) on failure.
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float(
OpusMSDecoder *st,
const unsigned char *data,
opus_int32 len,
float *pcm,
int frame_size,
int decode_fec
) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);
/** Perform a CTL function on a multistream Opus decoder.
*
* Generally the request and subsequent arguments are generated by a
* convenience macro.
* @param st <tt>OpusMSDecoder*</tt>: Multistream decoder state.
* @param request This and all remaining parameters should be replaced by one
* of the convenience macros in @ref opus_genericctls,
* @ref opus_decoderctls, or @ref opus_multistream_ctls.
* @see opus_genericctls
* @see opus_decoderctls
* @see opus_multistream_ctls
*/
OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1);
/** Frees an <code>OpusMSDecoder</code> allocated by
* opus_multistream_decoder_create().
* @param st <tt>OpusMSDecoder</tt>: Multistream decoder state to be freed.
*/
OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st);
/**@}*/
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* OPUS_MULTISTREAM_H */

159
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_types.h

@ -0,0 +1,159 @@
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
/* Modified by Jean-Marc Valin */
/*
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.
*/
/* opus_types.h based on ogg_types.h from libogg */
/**
@file opus_types.h
@brief Opus reference implementation types
*/
#ifndef OPUS_TYPES_H
#define OPUS_TYPES_H
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
#if (defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
#include <stdint.h>
typedef int16_t opus_int16;
typedef uint16_t opus_uint16;
typedef int32_t opus_int32;
typedef uint32_t opus_uint32;
#elif defined(_WIN32)
# if defined(__CYGWIN__)
# include <_G_config.h>
typedef _G_int32_t opus_int32;
typedef _G_uint32_t opus_uint32;
typedef _G_int16 opus_int16;
typedef _G_uint16 opus_uint16;
# elif defined(__MINGW32__)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
# elif defined(__MWERKS__)
typedef int opus_int32;
typedef unsigned int opus_uint32;
typedef short opus_int16;
typedef unsigned short opus_uint16;
# else
/* MSVC/Borland */
typedef __int32 opus_int32;
typedef unsigned __int32 opus_uint32;
typedef __int16 opus_int16;
typedef unsigned __int16 opus_uint16;
# endif
#elif defined(__MACOS__)
# include <sys/types.h>
typedef SInt16 opus_int16;
typedef UInt16 opus_uint16;
typedef SInt32 opus_int32;
typedef UInt32 opus_uint32;
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
# include <sys/types.h>
typedef int16_t opus_int16;
typedef u_int16_t opus_uint16;
typedef int32_t opus_int32;
typedef u_int32_t opus_uint32;
#elif defined(__BEOS__)
/* Be */
# include <inttypes.h>
typedef int16 opus_int16;
typedef u_int16 opus_uint16;
typedef int32_t opus_int32;
typedef u_int32_t opus_uint32;
#elif defined (__EMX__)
/* OS/2 GCC */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#elif defined (DJGPP)
/* DJGPP */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#elif defined(R5900)
/* PS2 EE */
typedef int opus_int32;
typedef unsigned opus_uint32;
typedef short opus_int16;
typedef unsigned short opus_uint16;
#elif defined(__SYMBIAN32__)
/* Symbian GCC */
typedef signed short opus_int16;
typedef unsigned short opus_uint16;
typedef signed int opus_int32;
typedef unsigned int opus_uint32;
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef long opus_int32;
typedef unsigned long opus_uint32;
#elif defined(CONFIG_TI_C6X)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#else
/* Give up, take a reasonable guess */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#endif
#define opus_int int /* used for counters etc; at least 16 bits */
#define opus_int64 long long
#define opus_int8 signed char
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
#define opus_uint64 unsigned long long
#define opus_uint8 unsigned char
#endif /* OPUS_TYPES_H */

BIN
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Info.plist

Binary file not shown.

6
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Modules/module.modulemap

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

BIN
local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/opus

Binary file not shown.

136
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt

@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log import android.util.Log
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import com.yunqiinnovation.qq_music.QQMusicSingleton import com.yunqiinnovation.qq_music.QQMusicSingleton
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.resume
/** /**
* QQ音乐系统功能处理器 * QQ音乐系统功能处理器
* 负责注册QQ音乐相关的MCP函数 * 负责注册QQ音乐相关的MCP函数
@ -195,60 +197,110 @@ private class QQMusicLoginHandler(private val context: Context?) : FunctionHandl
} }
} }
} }
private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { override suspend fun handle(arguments: Map<String, Any>): String {
return try { return try {
val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}" val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}"
val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲 val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲
val qqMusicSingleton = QQMusicSingleton.getInstance() val qqMusicSingleton = QQMusicSingleton.getInstance()
var result = ""
// 直接调用搜索,内部会自动处理登录检查
qqMusicSingleton.searchSongs(keyword) { searchResult ->
result = if (searchResult.isSuccess) {
val songs = searchResult.getOrNull() ?: emptyList()
// 根据搜索类型处理结果 // 使用 suspendCoroutine 将回调转换为协程
val processedSongs = when (searchType) { suspendCoroutine { continuation ->
0 -> { // 歌曲搜索,只返回第一个结果 qqMusicSingleton.searchSongs(keyword) { searchResult ->
// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList() val result = if (searchResult.isSuccess) {
songs val songs = searchResult.getOrNull() ?: emptyList()
}
else -> { // 专辑、歌手等其他类型,返回所有结果 // 根据搜索类型处理结果
songs val processedSongs = when (searchType) {
0 -> { // 歌曲搜索,返回所有结果
songs
}
else -> { // 专辑、歌手等其他类型,返回所有结果
songs
}
} }
}
val songsJson = processedSongs.joinToString(",") { song -> val songsJson = processedSongs.joinToString(",") { song ->
val songMap = song as Map<String, Any?> val songMap = song as Map<String, Any?>
// 添加调试日志 // 添加调试日志
Log.d("QQMusicSearchHandler", "原始数据: $songMap") Log.d("QQMusicSearchHandler", "原始数据: $songMap")
// 修正字段名映射 // 修正字段名映射
val mid = songMap["mid"] ?: "unknown" val mid = songMap["mid"] ?: "unknown"
val name = songMap["name"] ?: "unknown" val name = songMap["name"] ?: "unknown"
val artist = songMap["singer"] ?: "unknown" val artist = songMap["singer"] ?: "unknown"
val album = songMap["album"] ?: "" val album = songMap["album"] ?: ""
val pic = songMap["pic"] ?: "" val pic = songMap["pic"] ?: ""
val duration = songMap["duration"] ?: 0 val duration = songMap["duration"] ?: 0
"{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}"
}
"{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}"
} else {
val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
} }
"{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}" // 恢复协程执行
} else { continuation.resume(result)
val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
} }
} }
// 等待回调完成
delay(5000)
result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e)
"{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}"
} }
} }
} }
//private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler {
// override suspend fun handle(arguments: Map<String, Any>): String {
// return try {
// val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}"
// val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲
// val qqMusicSingleton = QQMusicSingleton.getInstance()
// var result = ""
//
// // 直接调用搜索,内部会自动处理登录检查
// qqMusicSingleton.searchSongs(keyword) { searchResult ->
// result = if (searchResult.isSuccess) {
// val songs = searchResult.getOrNull() ?: emptyList()
//
// // 根据搜索类型处理结果
// val processedSongs = when (searchType) {
// 0 -> { // 歌曲搜索,只返回第一个结果
//// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList()
// songs
// }
// else -> { // 专辑、歌手等其他类型,返回所有结果
// songs
// }
// }
//
// val songsJson = processedSongs.joinToString(",") { song ->
// val songMap = song as Map<String, Any?>
// // 添加调试日志
// Log.d("QQMusicSearchHandler", "原始数据: $songMap")
// // 修正字段名映射
// val mid = songMap["mid"] ?: "unknown"
// val name = songMap["name"] ?: "unknown"
// val artist = songMap["singer"] ?: "unknown"
// val album = songMap["album"] ?: ""
// val pic = songMap["pic"] ?: ""
// val duration = songMap["duration"] ?: 0
// "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}"
// }
// "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}"
// } else {
// val error = searchResult.exceptionOrNull()
// "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
// }
// }
//
// // 等待回调完成
// delay(5000)
// result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" }
// } catch (e: Exception) {
// Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e)
// "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}"
// }
// }
//}
private class QQMusicPlaySongsHandler(private val context: Context?) : FunctionHandler { private class QQMusicPlaySongsHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { override suspend fun handle(arguments: Map<String, Any>): String {
@ -310,7 +362,7 @@ private class QQMusicPauseHandler(private val context: Context?) : FunctionHandl
qqMusicSingleton.pauseMusic { pauseResult -> qqMusicSingleton.pauseMusic { pauseResult ->
result = if (pauseResult.isSuccess) { result = if (pauseResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"暂停播放\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"暂停播放\"}"
} else { } else {
val error = pauseResult.exceptionOrNull() val error = pauseResult.exceptionOrNull()
"{\"success\": false, \"message\": \"暂停失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"暂停失败: ${error?.message}\"}"
@ -334,7 +386,7 @@ private class QQMusicResumeHandler(private val context: Context?) : FunctionHand
qqMusicSingleton.resumeMusic { resumeResult -> qqMusicSingleton.resumeMusic { resumeResult ->
result = if (resumeResult.isSuccess) { result = if (resumeResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"恢复播放\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"恢复播放\"}"
} else { } else {
val error = resumeResult.exceptionOrNull() val error = resumeResult.exceptionOrNull()
"{\"success\": false, \"message\": \"恢复播放失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"恢复播放失败: ${error?.message}\"}"
@ -358,7 +410,7 @@ private class QQMusicStopHandler(private val context: Context?) : FunctionHandle
qqMusicSingleton.stopMusic { stopResult -> qqMusicSingleton.stopMusic { stopResult ->
result = if (stopResult.isSuccess) { result = if (stopResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"停止播放\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"停止播放\"}"
} else { } else {
val error = stopResult.exceptionOrNull() val error = stopResult.exceptionOrNull()
"{\"success\": false, \"message\": \"停止失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"停止失败: ${error?.message}\"}"
@ -382,7 +434,7 @@ private class QQMusicNextHandler(private val context: Context?) : FunctionHandle
qqMusicSingleton.skipToNext { nextResult -> qqMusicSingleton.skipToNext { nextResult ->
result = if (nextResult.isSuccess) { result = if (nextResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"切换到下一首\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"切换到下一首\"}"
} else { } else {
val error = nextResult.exceptionOrNull() val error = nextResult.exceptionOrNull()
"{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}"
@ -406,7 +458,7 @@ private class QQMusicPreviousHandler(private val context: Context?) : FunctionHa
qqMusicSingleton.skipToPrevious { previousResult -> qqMusicSingleton.skipToPrevious { previousResult ->
result = if (previousResult.isSuccess) { result = if (previousResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"切换到上一首\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"切换到上一首\"}"
} else { } else {
val error = previousResult.exceptionOrNull() val error = previousResult.exceptionOrNull()
"{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}"
@ -434,7 +486,7 @@ private class QQMusicGetPlaybackStateHandler(private val context: Context?) : Fu
val stateJson = state.entries.joinToString(",") { (key, value) -> val stateJson = state.entries.joinToString(",") { (key, value) ->
"\"$key\": \"$value\"" "\"$key\": \"$value\""
} }
"{\"notreply\": true, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}" "{\"broadcast\": false, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}"
} else { } else {
val error = stateResult.exceptionOrNull() val error = stateResult.exceptionOrNull()
"{\"success\": false, \"message\": \"获取播放状态失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"获取播放状态失败: ${error?.message}\"}"

37
local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt

@ -319,6 +319,43 @@ class QQMusicManager(private val activity: Activity) {
} }
} }
fun autopauseMusic(callback: PlayCallback){
if (!isBindQQMusicService || qqmusicApi == null) {
callback.onError(-1, "QQ音乐服务未连接")
return
}
// 获取播放状态
val stateResult = qqmusicApi?.execute("getPlaybackState", null)
val stateCode = stateResult?.getInt(Keys.API_RETURN_KEY_CODE) ?: -1
// 处理播放状态 - 使用正确的常量
if (stateCode == ErrorCodes.ERROR_OK) {
val playState = stateResult?.getInt(Keys.API_RETURN_KEY_DATA) ?: 0
val isPlaying = when (playState) {
PlayState.STARTED -> true
PlayState.PREPARED -> true
PlayState.BUFFERING -> true
PlayState.PAUSED -> false
PlayState.STOPPED -> false
PlayState.ERROR -> false
else -> false
}
if(isPlaying){
val result = qqmusicApi?.execute("pauseMusic", null)
val code = result?.getInt(Keys.API_RETURN_KEY_CODE) ?: -1
if (code == ErrorCodes.ERROR_OK) {
callback.onSuccess()
} else {
callback.onError(code, "暂停失败")
}
}else {
callback.onError(-1, "未播放")
}
} else {
callback.onError(-1, "未播放")
}
}
/** /**
* 暂停音乐 * 暂停音乐
*/ */

17
local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt

@ -25,8 +25,8 @@ class QQMusicSingleton private constructor() {
private const val KEY_EXPIRE_TIME = "expire_time" private const val KEY_EXPIRE_TIME = "expire_time"
private const val LOGIN_WAIT_DELAY_MS = 30000L private const val LOGIN_WAIT_DELAY_MS = 30000L
// 添加搜索重试相关常量 // 添加搜索重试相关常量
private const val SEARCH_TIMEOUT_MS = 50000L private const val SEARCH_TIMEOUT_MS = 10000L
private const val MAX_RETRY_COUNT = 3 private const val MAX_RETRY_COUNT = 1
private const val RETRY_DELAY_MS = 1000L private const val RETRY_DELAY_MS = 1000L
@Volatile @Volatile
@ -194,7 +194,7 @@ class QQMusicSingleton private constructor() {
callback(Result.failure(Exception("未安装QQ音乐软件,请先安装QQ音乐软件"))) callback(Result.failure(Exception("未安装QQ音乐软件,请先安装QQ音乐软件")))
return return
} }
searchWithRetry(keyword, searchType, 0, callback) searchWithRetry(keyword, searchType, 1, callback)
} }
/** /**
@ -413,7 +413,16 @@ class QQMusicSingleton private constructor() {
musicManager.pauseMusic(playCallback) musicManager.pauseMusic(playCallback)
}(callback) }(callback)
} }
/**
* 暂停音乐
*/
fun autopauseMusic(callback: (Result<Unit>) -> Unit) {
executePlaybackAction("自动暂停") { musicManager, playCallback ->
musicManager.autopauseMusic(playCallback)
}(callback)
}
/** /**
* 恢复播放 * 恢复播放
*/ */

2
pubspec.yaml

@ -1,7 +1,7 @@
name: voitrans name: voitrans
description: "Voitrans - AI Voice Assistant." description: "Voitrans - AI Voice Assistant."
publish_to: "none" publish_to: "none"
version: 1.0.9+20 version: 1.0.8+20
environment: environment:
sdk: ">=3.3.0 <4.0.0" sdk: ">=3.3.0 <4.0.0"

Loading…
Cancel
Save