diff --git a/.gitignore b/.gitignore index ac4aa4092..61024af15 100644 --- a/.gitignore +++ b/.gitignore @@ -222,3 +222,4 @@ fastlane/readme.md .trae/rules/project_rules.md .augment/rules/imported/project_rules.md .kiro/ +ios/Runner.xcodeproj/project.pbxproj diff --git a/.metadata b/.metadata index 0d012fee6..369b5ecec 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "5c1433509f997e9059d230942bdf83f1b88a179e" + revision: "8defaa71a77c16e8547abdbfad2053ce3a6e2d5b" channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e - base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b - platform: android - create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e - base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + 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 diff --git a/SpeexKit.xcframework/Info.plist b/SpeexKit.xcframework/Info.plist new file mode 100644 index 000000000..c654fe5c9 --- /dev/null +++ b/SpeexKit.xcframework/Info.plist @@ -0,0 +1,27 @@ + + + + + AvailableLibraries + + + BinaryPath + SpeexKit.framework/SpeexKit + LibraryIdentifier + ios-arm64 + LibraryPath + SpeexKit.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusTools.h b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusTools.h new file mode 100644 index 000000000..55fa7f1e0 --- /dev/null +++ b/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 + + +NS_ASSUME_NONNULL_BEGIN + +@protocol OpusToolsDelegate + +-(void)opusToolsPcmData:(NSData*)data; + +-(void)opusToolsPcmFile:(NSData *)data; + +-(void)opusToolsOpusData:(NSData *)data; + +-(void)opusToolsOpusFile:(NSData *)data; + +@end + +@interface OpusTools : NSObject + +@property(nonatomic,weak)id 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 diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusUnit.h b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusUnit.h new file mode 100644 index 000000000..37b12201a --- /dev/null +++ b/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 + +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 diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexKit.h b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexKit.h new file mode 100644 index 000000000..0fb1c92bd --- /dev/null +++ b/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 + +//! 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 + +#import +#import +#import diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexUnit.h b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexUnit.h new file mode 100644 index 000000000..da7e93dae --- /dev/null +++ b/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 + +/** + * 流式解码回调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 diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist new file mode 100644 index 000000000..f1478bd99 Binary files /dev/null and b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist differ diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap new file mode 100644 index 000000000..db8a36801 --- /dev/null +++ b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module SpeexKit { + umbrella header "SpeexKit.h" + export * + + module * { export * } +} diff --git a/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit new file mode 100644 index 000000000..194c091f6 Binary files /dev/null and b/SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit differ diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index a5b86bc55..9fbd2f937 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -208,6 +208,233 @@ const Map enUS = { 'aiSummary': 'AI Summary', '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", "premiumMember": "Premium Membership", @@ -305,7 +532,7 @@ const Map enUS = { "loadFailedClickRetry": "Load failed, click to retry", "swipeUpForMore": "Swipe up for more", - // Preset prompts + // 预设提示词 "presetMusic": "Play some music", // Play some music "presetNavigation": "Navigation to", // Navigation "presetWeather": "Today's weather", // Weather @@ -758,7 +985,7 @@ const Map enUS = { "renameAudioFile": "Rename Audio File", "enterNewFileName": "Please enter new file name", - // 翻译详情页 + // 翻译详情页(translation_detail_view.dart) "originalRecord": "Original Record", "recordSummary": "Record Summary", "clickToGenerateSummary": @@ -955,11 +1182,4 @@ const Map enUS = { "justNow": "Just now", "minutesAgo": "@minutes minutes ago", "yesterday": "Yesterday", - // "monday": "Monday", - // "tuesday": "Tuesday", - // "wednesday": "Wednesday", - // "thursday": "Thursday", - // "friday": "Friday", - // "saturday": "Saturday", - // "sunday": "Sunday", }; diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 2dc75a89b..b78dbf411 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -202,6 +202,214 @@ const Map zhCN = { 'aiSummary': 'AI总结', '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": "系统设置", "premiumMember": "高级会员", diff --git a/lib/core/utils/permission_util.dart b/lib/core/utils/permission_util.dart index 2a88c5cd9..1f19ff396 100644 --- a/lib/core/utils/permission_util.dart +++ b/lib/core/utils/permission_util.dart @@ -176,7 +176,9 @@ class PermissionUtil { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'permissionUtilNeed'.tr + permissionName + 'permissionUtilPermission'.tr, + 'permissionUtilNeed'.tr + + permissionName + + 'permissionUtilPermission'.tr, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, @@ -261,7 +263,9 @@ class PermissionUtil { bool? userChoice = await Get.dialog( AlertDialog( - title: Text('permissionUtilNeed'.tr + permissionName + 'permissionUtilPermission'.tr), + title: Text('permissionUtilNeed'.tr + + permissionName + + 'permissionUtilPermission'.tr), content: Text(permanentDenialText), actions: [ TextButton( diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 8d4f67654..ef8cf5ac1 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -701,6 +701,12 @@ class AgentController extends GetxController { final functionName = event.data['name'] ?? ''; final arguments = event.data['arguments'] ?? ''; // debugPrint('Fultter 开始调用 mcp: $functionName, $arguments'); + if (functionName == 'exit_interaction') { + if (isPhoneCallMode.value) { + endPhoneCall(); //退出语音交互,同时退出打电话模式 + } + } + break; case AgentServiceEventType.functionCallResult: diff --git a/lib/modules/explore/views/explore_view.dart b/lib/modules/explore/views/explore_view.dart index d57c9561f..0f373dbf1 100644 --- a/lib/modules/explore/views/explore_view.dart +++ b/lib/modules/explore/views/explore_view.dart @@ -23,9 +23,9 @@ class ExploreView extends GetView { // 主要内容(可滚动) Expanded( child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), + //physics: const BouncingScrollPhysics(), child: Padding( - padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 80.h), + padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 18252c4cd..83a2330e2 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -103,6 +103,10 @@ class HomeController extends GetxController { Future refreshDeviceConnection() async { await _checkConnectionStatus(); + // 如果设备已连接,立即刷新电池信息 + if (_isFullyConnected.value) { + await _refreshBatteryInfo(); + } } void rescanDevices() { @@ -137,6 +141,16 @@ class HomeController extends GetxController { if (!wasConnected && isNowConnected) { 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) { _isFullyConnected.value = false; _isFullyConnected.refresh(); + _stopBatteryRefreshTimer(); // 发生错误时停止定时器 update(); } } @@ -298,18 +313,18 @@ class HomeController extends GetxController { return; } - Logger.d(_tag, '开始刷新电池信息...'); + //Logger.d(_tag, '开始刷新电池信息...'); // 通过BLE管理器获取最新的电池信息 final success = await _bleManager.fetchBatteryInfo(); if (success) { - Logger.d(_tag, '电池信息刷新请求发送成功'); + //Logger.d(_tag, '电池信息刷新请求发送成功'); } else { - Logger.w(_tag, '电池信息刷新请求发送失败'); + //Logger.w(_tag, '电池信息刷新请求发送失败'); } } catch (e) { - Logger.e(_tag, '刷新电池信息时发生错误: $e'); + //Logger.e(_tag, '刷新电池信息时发生错误: $e'); } } } diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index e51be533d..8506a7337 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -76,7 +76,7 @@ class HomeView extends GetView { return Scaffold( backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFC), body: SafeArea( - child: SingleChildScrollView( + child: Padding( padding: EdgeInsets.all(16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -84,12 +84,12 @@ class HomeView extends GetView { // 顶部标题栏 _buildAppBar(isDarkMode), - // 顶部图片 - _buildEarbudsImage(), + // 可变高度的顶部图片区域 + Obx(() => _buildAdaptiveEarbudsImage()), SizedBox(height: 20.h), - // 设备状态卡片 + // 设备状态卡片 - 使用 AnimatedContainer 实现平滑过渡 IntroStepBuilder( group: 'home', order: 1, @@ -107,7 +107,7 @@ class HomeView extends GetView { }, builder: (BuildContext context, GlobalKey key) => SizedBox( key: key, - child: _buildDeviceStatusCard(isDarkMode), + child: _buildAnimatedDeviceStatusCard(isDarkMode), ), ), @@ -143,10 +143,17 @@ class HomeView extends GetView { ), ), - // 热门AI体验 - _buildSectionTitle(isDarkMode), - SizedBox(height: 16.h), - _buildPremiumAILayout(), + // 热门AI体验 - 使用 Expanded 确保始终在可视范围内 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle(isDarkMode), + SizedBox(height: 16.h), + Expanded(child: _buildPremiumAILayout()), + ], + ), + ), ], ), ), @@ -196,70 +203,48 @@ class HomeView extends GetView { ); } - // 耳机图片 - Widget _buildEarbudsImage() { + + + // 自适应高度的耳机图片 - 根据设备连接状态动态调整高度 + Widget _buildAdaptiveEarbudsImage() { 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( 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( child: ClipRRect( - // 添加 ClipRRect borderRadius: BorderRadius.circular(16.r), child: Image.asset( isDarkMode ? 'assets/images/headphone_new3.png' : 'assets/images/headphone_new4.png', - height: 220.h, + height: imageHeight, 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(() { - // 使用连接状态作为key,强制在状态改变时重建widget final isConnected = controller.isFullyConnectedRx.value; - return Container( - key: ValueKey('device_status_$isConnected'), + + return AnimatedContainer( + duration: const Duration(milliseconds: 500), // 平滑过渡动画 + curve: Curves.easeInOut, decoration: BoxDecoration( color: isDarkMode ? const Color(0xFF1A1A1A) : Colors.white, borderRadius: BorderRadius.circular(16.r), @@ -273,30 +258,58 @@ class HomeView extends GetView { ), padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 16.h), child: Column( + mainAxisSize: MainAxisSize.min, // 重要:让容器高度自适应内容 children: [ // 状态标题行 _buildDeviceStatusHeader(isDarkMode), - // 设备信息 + // 设备信息 - 使用 AnimatedSwitcher 实现内容切换动画 SizedBox(height: 12.h), - Obx(() { - // 移除连接失败和连接中的状态判断,直接根据是否完全连接来判断 - // 使用响应式访问确保UI更新 - return controller.isFullyConnectedRx.value - ? _buildConnectedDeviceInfo(isDarkMode) //已连接设备信息 - : _buildDisconnectedInfo(isDarkMode); //未连接提示 - }), - - // 连接按钮(仅在未连接时显示) + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + 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), - Obx(() { - // 使用响应式访问确保UI更新 - if (!controller.isFullyConnectedRx.value) { - return _buildConnectButton(); - } else { - return _buildDeviceButton(isDarkMode); - } - }), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + 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), + ), + ), ], ), ); diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart index b605931ca..6462b66f1 100644 --- a/lib/modules/login/controllers/login_controller.dart +++ b/lib/modules/login/controllers/login_controller.dart @@ -37,6 +37,7 @@ class LoginController extends GetxController { String _deviceId = ''; String _appVersion = ''; // APP版本号 String _countryCode = ''; // 国家代码 + String _languageCode = ''; // 语言代码 UserChannel _userChannel = UserChannel.OFFICIAL; // 默认渠道 final isChinaMainland = false.obs; //是否中国大陆地区 @@ -310,6 +311,10 @@ class LoginController extends GetxController { _countryCode = Get.locale?.countryCode ?? 'Unknown'; Logger().d('countryCode: $_countryCode'); + // 获取语言代码 + _languageCode = Get.locale?.languageCode ?? 'Unknown'; + Logger().d('languageCode: $_languageCode'); + // 获取用户渠道(根据平台和设备信息确定) _userChannel = versionService.cachedChannel; Logger().d('userChannel: ${_userChannel.value}'); @@ -317,6 +322,8 @@ class LoginController extends GetxController { Logger().e('获取APP信息失败: $e'); _appVersion = 'Unknown'; _countryCode = 'Unknown'; + _languageCode = 'Unknown'; + _userChannel = UserChannel.UNKNOWN; } } @@ -790,8 +797,7 @@ class LoginController extends GetxController { "stype": loginType, "vcode": "", "ttoken": "", - "phonemodel": - _appVersion + _countryCode + _userChannel.toString(), + "phonemodel": _countryCode + _userChannel.toString(), "phonemac": _deviceId, "name": "", "avatar": "", @@ -834,10 +840,13 @@ class LoginController extends GetxController { "stype": params["stype"], "vcode": params["vcode"], "ttoken": params["ttoken"], - "phonemodel": _appVersion + _countryCode + _userChannel.toString(), + "phonemodel": _countryCode, "phonemac": params["phonemac"], "name": params["name"], "avatar": params["avatar"], + "language": _languageCode, + "channel": _userChannel, + "version": _appVersion, }).timeout( const Duration(seconds: 30), // 30秒超时 onTimeout: () { diff --git a/lib/modules/meeting/controllers/meeting_connect_controller.dart b/lib/modules/meeting/controllers/meeting_connect_controller.dart index 56c7b213f..440b917b5 100644 --- a/lib/modules/meeting/controllers/meeting_connect_controller.dart +++ b/lib/modules/meeting/controllers/meeting_connect_controller.dart @@ -13,7 +13,7 @@ import 'meeting_home_controller.dart'; class MeetingConnectController extends GetxController { final _meetingController = Get.find(); - RxString stateStr = '搜索中...'.obs; + RxString stateStr = 'searching'.tr.obs; // 对应中文:搜索中... RxBool isRescan = false.obs; RxString importedFilesStr = ''.obs; @@ -50,15 +50,15 @@ class MeetingConnectController extends GetxController { }); _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) { if (isScanFailed) { - stateStr.value = '搜索失败,请检查蓝牙是否开启'; + stateStr.value = 'searchFailed'.tr; // 对应中文:搜索失败,请检查蓝牙是否开启 isRescan.value = true; return; } if (!state && _device == null) { - stateStr.value = '未搜索到 AI PEN 设备'; + stateStr.value = 'aiPenNotFound'.tr; // 对应中文:未搜索到 AI PEN 设备 isRescan.value = true; } else { - stateStr.value = '搜索中...'; + stateStr.value = 'searching'.tr; // 对应中文:搜索中... isRescan.value = false; } }); @@ -111,7 +111,7 @@ class MeetingConnectController extends GetxController { _device = result.device; await _stopScan(); 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) { if (element.characteristicUuid == Guid('FFF1')) { _writeCharacteristic = element; - stateStr.value = '检查是否有新文件需要导入'; + stateStr.value = 'checkingNewFiles'.tr; // 对应中文:检查是否有新文件需要导入 sendData([0x5a, 0x01, 0x0d]); } else if (element.characteristicUuid == Guid('FFF2')) { await element.setNotifyValue(true); @@ -151,11 +151,11 @@ class MeetingConnectController extends GetxController { switch (command) { case 0x0d: // 是否有导入文件 if (value[3] == 0x01) { - stateStr.value = '正在开启Wi-Fi...'; - importedFilesStr.value = '检查到有新文件,正在准备导入'; + stateStr.value = 'startingWifi'.tr; // 对应中文:正在开启Wi-Fi... + importedFilesStr.value = 'newFilesDetected'.tr; // 对应中文:检查到有新文件,正在准备导入 sendData([0x5a, 0x02, 0x87, 0x01]); } else { - importedFilesStr.value = '没有新文件需要导入'; + importedFilesStr.value = 'noNewFiles'.tr; // 对应中文:没有新文件需要导入 } break; case 0x87: // Wi-Fi 开启/关闭 @@ -193,14 +193,14 @@ class MeetingConnectController extends GetxController { void _connectWifi() async { if (_wifiSSID.isNotEmpty && _wifiPassword.isNotEmpty) { - stateStr.value = '正在连接 Wi-Fi...'; + stateStr.value = 'connectingWifi'.tr; // 对应中文:正在连接 Wi-Fi... await WiFiForIoTPlugin.connect( _wifiSSID, password: _wifiPassword, security: NetworkSecurity.WPA, ); await WiFiForIoTPlugin.forceWifiUsage(true); - stateStr.value = 'Wi-Fi 连接成功'; + stateStr.value = 'wifiConnected'.tr; // 对应中文:Wi-Fi 连接成功 sendData([0x5a, 0x01, 0x03]); } } @@ -220,7 +220,7 @@ class MeetingConnectController extends GetxController { _ftpConnect?.listCommand = ListCommand.LIST; List? listDirectory = await _ftpConnect?.listDirectoryContent(); if (listDirectory != null && listDirectory.isNotEmpty) { - stateStr.value = '正在下载文件...'; + stateStr.value = 'downloadingFiles'.tr; // 对应中文:正在下载文件... isDownload.value = true; int totalSize = listDirectory.fold( 0, @@ -228,15 +228,15 @@ class MeetingConnectController extends GetxController { ); for (var i = 0; i < listDirectory.length; i++) { 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; FTPEntry element = listDirectory[i]; await _downloadFile(totalSize, previousSize, element); await _ftpConnect?.deleteFile(element.name); } await _disconnect(); - stateStr.value = '下载完成'; - importedFilesStr.value = '下载完成,共${listDirectory.length}个文件,2秒后自动返回'; + stateStr.value = 'downloadComplete'.tr; // 对应中文:下载完成 + importedFilesStr.value = 'downloadCompleteInfo'.trParams({'count': '${listDirectory.length}'}); // 对应中文:下载完成,共{count}个文件,2秒后自动返回 Future.delayed(const Duration(seconds: 2), () { _executeUpload(); Get.back(); diff --git a/lib/modules/meeting/controllers/meeting_home_controller.dart b/lib/modules/meeting/controllers/meeting_home_controller.dart index e994d5025..470b312d5 100644 --- a/lib/modules/meeting/controllers/meeting_home_controller.dart +++ b/lib/modules/meeting/controllers/meeting_home_controller.dart @@ -110,9 +110,9 @@ class MeetingHomeController extends GetxController { PermissionUtil.instance .requestPermission( permissionType: Permission.storage, - permissionName: "存储", - explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。", - permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。", + permissionName: "meetingStoragePermission".tr, // 对应中文:存储 + explanationText: "meetingStoragePermissionExplanation".tr, // 对应中文:我们需要访问存储权限,以便您可以选择并上传音频文件。 + permanentDenialText: "meetingStoragePermissionPermanentDenial".tr, // 对应中文:检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。 ) .then((granted) { if (granted) { @@ -164,17 +164,17 @@ class MeetingHomeController extends GetxController { try { await Share.shareXFiles( [XFile(filePath)], - text: '分享会议音频', + text: 'shareAudio'.tr, // 对应中文:分享会议音频 ); - EasyLoading.showToast('分享完成!'); + EasyLoading.showToast('shareComplete'.tr); // 对应中文:分享完成! } catch (e) { - EasyLoading.showToast('分享失败: ${e.toString()}'); + EasyLoading.showToast('shareFailed'.trParams({'error': e.toString()})); // 对应中文:分享失败: {error} } } void addMeetingAudio() async { - EasyLoading.show(status: '正在导入本地录音...'); + EasyLoading.show(status: 'importingLocalRecordings'.tr); // 对应中文:正在导入本地录音... try { int addUp = 0; final appDir = await appDocDir; @@ -258,19 +258,19 @@ class MeetingHomeController extends GetxController { addUp++; } } 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) { - EasyLoading.showError('导入失败: $e'); + EasyLoading.showError('importError'.trParams({'error': e.toString()})); // 对应中文:导入失败: {error} } finally { EasyLoading.dismiss(); } } void addTranslatAudio() async { - EasyLoading.show(status: '正在批量导入...'); + EasyLoading.show(status: 'batchImporting'.tr); // 对应中文:正在批量导入... for (final filePath in selected) { try { await _audioPlayer.setAudioSource(AudioSource.file(filePath)); @@ -308,14 +308,14 @@ class MeetingHomeController extends GetxController { } } } catch (e) { - Logger.error('导入文件失败: $filePath, error: $e'); + Logger.error('importFailed'.trParams({'filePath': filePath, 'error': e.toString()})); // 对应中文:导入文件失败: {filePath}, error: {error} } } EasyLoading.dismiss(); } void addAudio([String? filePath]) async { - EasyLoading.show(status: '正在导入...'); + EasyLoading.show(status: 'importing'.tr); // 对应中文:正在导入... String fileName = ""; bool isParamProvided = filePath != null; // 更清晰的变量名 if (filePath != null) { @@ -331,7 +331,7 @@ class MeetingHomeController extends GetxController { if (!_isAudioFile(file.name)) { Get.snackbar( 'error'.tr, // 错误 - '请选择音频文件', + 'pleaseSelectAudioFile'.tr, // 对应中文:请选择音频文件 snackPosition: SnackPosition.TOP, backgroundColor: Colors.red.withOpacity(0.1), colorText: Colors.red, diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart index 47e656df3..eea8c50d2 100644 --- a/lib/modules/meeting/controllers/meeting_record_controller.dart +++ b/lib/modules/meeting/controllers/meeting_record_controller.dart @@ -154,13 +154,13 @@ class MeetingRecordController extends GetxController // Set default filename based on audio type switch (audioType.value) { case 0: - fileName.value = "现场录音"; + fileName.value = "liveRecording".tr; //对应中文:现场录音 break; case 1: - fileName.value = "音、视频录音"; + fileName.value = "audioVideoRecording".tr; //对应中文:音视频录音 break; case 2: - fileName.value = "通话录音"; + fileName.value = "callRecording".tr; //对应中文:电话录音 break; } newName.value = fileName.value; @@ -460,13 +460,13 @@ class MeetingRecordController extends GetxController void _resetToDefaultState() { switch (audioType.value) { case 0: - fileName.value = "现场录音"; + fileName.value = "liveRecording".tr; //对应中文:现场录音 break; case 1: - fileName.value = "音、视频录音"; + fileName.value = "audioVideoRecording".tr; //对应中文:音视频录音 break; case 2: - fileName.value = "通话录音"; + fileName.value = "callRecording".tr; //对应中文:电话录音 break; } newName.value = fileName.value; diff --git a/lib/modules/meeting/controllers/mind_map_controller.dart b/lib/modules/meeting/controllers/mind_map_controller.dart index b35c53e3e..45786ff99 100644 --- a/lib/modules/meeting/controllers/mind_map_controller.dart +++ b/lib/modules/meeting/controllers/mind_map_controller.dart @@ -175,11 +175,12 @@ class MindMapController extends GetxController { final File file = File('${tempDir.path}/$fileName'); await file.writeAsBytes(bytes); final result = await SharePlus.instance.share(ShareParams( - text: '分享思维导图', + text: 'shareMindMap'.tr, //对应中文:分享思维导图 + files: [XFile(file.path)], )); if (result.status == ShareResultStatus.success) { - EasyLoading.showToast('分享完成!'); + EasyLoading.showToast('shareSuccess'.tr); //对应中文:分享成功 } } } diff --git a/lib/modules/meeting/views/bottomSheet/ai_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/ai_bottom_sheet.dart index ecf83a2e7..b5003f21f 100644 --- a/lib/modules/meeting/views/bottomSheet/ai_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/ai_bottom_sheet.dart @@ -30,9 +30,9 @@ class _AIBottomSheetState extends State { List _dataList = []; final List _cruxList = [ - '代办事项', - '提取结论', - '生成关键指标', + 'todoList'.tr, //对应中文:代办事项 + 'extractConclusion'.tr, //对应中文:提取结论 + 'generateKeyIndicators'.tr, //对应中文:生成关键指标 ]; final StringBuffer _buffer = StringBuffer(); bool _isAdd = true; @@ -179,7 +179,7 @@ class _AIBottomSheetState extends State { Padding( padding: EdgeInsets.only(bottom: 5.w), child: Text( - '继续提问', + 'continueAsk'.tr, //对应中文:继续提问 style: TextStyle( color: Colors.grey, fontSize: 13.sp, @@ -226,7 +226,7 @@ class _AIBottomSheetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '今天我能帮您做点什么呢?', + 'askAIPrompt'.tr, //对应中文:今天我能帮您做点什么呢? style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, @@ -310,7 +310,7 @@ class _AIBottomSheetState extends State { height: 1.4, ), decoration: InputDecoration( - hintText: '问 AI ...', + hintText: 'askAI'.tr, //对应中文:问 AI ... hintStyle: TextStyle( fontSize: 16.sp, color: Colors.grey[400], diff --git a/lib/modules/meeting/views/bottomSheet/filtrate_drawer.dart b/lib/modules/meeting/views/bottomSheet/filtrate_drawer.dart index b9b8df66f..6e1258503 100644 --- a/lib/modules/meeting/views/bottomSheet/filtrate_drawer.dart +++ b/lib/modules/meeting/views/bottomSheet/filtrate_drawer.dart @@ -47,7 +47,7 @@ class _FiltrateDrawerState extends State { fontSize: 14.sp, ), decoration: InputDecoration( - hintText: '在所有文件中搜索', + hintText: 'searchInAllFiles'.tr, // 对应中文:在所有文件中搜索 hintStyle: TextStyle( color: Colors.black54, fontSize: 14.sp, @@ -109,7 +109,7 @@ class _FiltrateDrawerState extends State { ), 6.horizontalSpace, Text( - '全部文件(${_homeController.originalDataList.length})', + '${'allFiles'.tr}(${_homeController.originalDataList.length})', // 对应中文:全部文件 style: TextStyle( color: Colors.black87, fontSize: 14.sp, @@ -129,7 +129,7 @@ class _FiltrateDrawerState extends State { Padding( padding: EdgeInsets.only(bottom: 10.w), child: Text( - '来自', + 'from'.tr, // 对应中文:来自 style: TextStyle( color: Colors.grey, fontSize: 14.sp, @@ -174,7 +174,7 @@ class _FiltrateDrawerState extends State { Padding( padding: EdgeInsets.symmetric(vertical: 10.w), child: Text( - '排序方式', + 'sortBy'.tr, // 对应中文:排序方式 style: TextStyle( color: Colors.grey, fontSize: 14.sp, @@ -197,7 +197,7 @@ class _FiltrateDrawerState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '创建时间', + 'creationTime'.tr, // 对应中文:创建时间 style: TextStyle( color: Colors.black87, fontSize: 14.sp, diff --git a/lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart index b5b5ce256..3537b4435 100644 --- a/lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart @@ -24,7 +24,7 @@ class _GenerateBottomSheetState extends State { final GetStorage _storage = GetStorage(); bool _isSpeaker = true; - String _languageText = '中文普通话(简体)'; + String _languageText = 'chineseSimplified'.tr; // 对应中文:中文普通话(简体) String _language = 'zh-CN'; final Map _icons = { @@ -149,7 +149,7 @@ class _GenerateBottomSheetState extends State { children: [ Expanded( child: Text( - '选择总结模版', + 'selectSummaryTemplate'.tr, // 对应中文:选择总结模版 style: TextStyle( fontSize: 14.sp, color: Colors.black87, @@ -186,7 +186,7 @@ class _GenerateBottomSheetState extends State { child: Row( children: [ Text( - '查看全部', + 'viewAll'.tr, // 对应中文:查看全部 style: TextStyle( fontSize: 12.sp, color: Colors.grey, @@ -226,7 +226,7 @@ class _GenerateBottomSheetState extends State { child: Column( children: [ _row( - '区分说话人', + 'speakerIdentification'.tr, // 对应中文:区分说话人 widget: Transform.scale( scale: 0.8, alignment: Alignment.centerRight, @@ -241,7 +241,7 @@ class _GenerateBottomSheetState extends State { ), ), _row( - '录音语言', + 'recordingLanguage'.tr, // 对应中文:录音语言 value: _languageText, isBorder: false, onTap: () { @@ -296,7 +296,7 @@ class _GenerateBottomSheetState extends State { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '立即生成', + 'meetingGenerateNow'.tr, // 对应中文:立即生成 style: TextStyle( fontSize: 16.sp, color: Colors.white, @@ -416,7 +416,7 @@ class _GenerateBottomSheetState extends State { ), ), child: Text( - '上次使用', + 'lastUsed'.tr, // 对应中文:上次使用 style: TextStyle( color: Colors.white, fontSize: 12.sp, diff --git a/lib/modules/meeting/views/bottomSheet/member_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/member_bottom_sheet.dart index e77769091..321c28606 100644 --- a/lib/modules/meeting/views/bottomSheet/member_bottom_sheet.dart +++ b/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.description, Colors.purpleAccent, '为行业细分场景设计的专业总结模版'), - _item(Icons.auto_awesome, Colors.purpleAccent, 'Autopilot 模版自动总结'), - _item(Icons.people, Colors.red, '区分不同说话人'), - _item(Icons.cloud_upload, Colors.red, '从APP、WEB导入音频进行转写和总结'), + _item(Icons.access_time, Colors.red, + 'monthlyTranscriptionDuration'.tr), //对应中文:每月300分钟转写时长 + _item(Icons.description, Colors.purpleAccent, + 'industrySpecificSummaryTemplate'.tr), //对应中文:为行业细分场景设计的专业总结模版 + _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: EdgeInsets.symmetric(vertical: 20.w), child: Row( @@ -92,7 +97,7 @@ class MemberBottomSheet extends StatelessWidget { ), ), child: Text( - '发现更多', + 'findMore'.tr, //对应中文:发现更多 style: TextStyle( fontSize: 16.sp, color: Colors.black, @@ -113,7 +118,7 @@ class MemberBottomSheet extends StatelessWidget { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '立即绑定', + 'bindImmediately'.tr, //对应中文:立即绑定 style: TextStyle( fontSize: 16.sp, color: Colors.white, diff --git a/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart index a02fff599..188ca945e 100644 --- a/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/more_bottom_sheet.dart @@ -25,7 +25,7 @@ class _MoreBottomSheetState extends State { mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 10.w), - _shareItem(Icons.refresh, '重新总结', + _shareItem(Icons.refresh, 'reSummarize'.tr, //重新总结 onTap: _detailsController.meetingDetails.value.tasktype >= 3 ? () { Get.back(); @@ -43,7 +43,7 @@ class _MoreBottomSheetState extends State { ); } : null), - _shareItem(Icons.delete_outline, '删除', onTap: () async { + _shareItem(Icons.delete_outline, 'delete'.tr, onTap: () async { _controller.deleteMeeting( _detailsController.meetingData.value.id, _detailsController.meetingData.value.filepath, @@ -85,7 +85,8 @@ class _MoreBottomSheetState extends State { title, style: TextStyle( fontSize: 14.sp, - color: Colors.black87.withValues(alpha: onTap != null ? 1 : 0.5), + color: + Colors.black87.withValues(alpha: onTap != null ? 1 : 0.5), ), ), ), diff --git a/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart index 5e92df93a..7f594cdc6 100644 --- a/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get/get_navigation/src/extension_navigation.dart'; -import 'package:get/state_manager.dart'; +import 'package:get/get.dart'; import '../../../../routes/app_routes.dart'; import '../../controllers/meeting_home_controller.dart'; @@ -18,14 +17,14 @@ class NavigationBarBottomSheet extends GetView { if (controller.meetingController.type == 0) _buildItem( context, // 传递 context 参数 - '开始录音', + 'startRecording'.tr, // 对应中文:开始录音 Icons.mic_rounded, Colors.red, ), if (controller.meetingController.type == 1) _buildItem( context, // 传递 context 参数 - '现场录音', + 'liveRecording'.tr, // 对应中文:现场录音 Icons.mic_rounded, Colors.red, onTap: () async { @@ -39,7 +38,7 @@ class NavigationBarBottomSheet extends GetView { if (controller.meetingController.type == 1) _buildItem( context, // 传递 context 参数 - '音、视频录音', + 'audioVideoRecording'.tr, // 对应中文:音、视频录音 Icons.videocam, Colors.green, onTap: controller.bleManager.isConnected @@ -55,7 +54,7 @@ class NavigationBarBottomSheet extends GetView { if (controller.meetingController.type == 1) _buildItem( context, // 传递 context 参数 - '通话录音', + 'callRecording'.tr, // 对应中文:通话录音 Icons.phone, Colors.orange, onTap: controller.bleManager.isConnected @@ -70,7 +69,7 @@ class NavigationBarBottomSheet extends GetView { ), _buildItem( context, // 传递 context 参数 - '导入音频', + 'importAudio'.tr, // 对应中文:导入音频 Icons.file_upload_outlined, Colors.deepPurple, onTap: () { diff --git a/lib/modules/meeting/views/bottomSheet/record_language_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/record_language_bottom_sheet.dart index 217613373..bfab9698d 100644 --- a/lib/modules/meeting/views/bottomSheet/record_language_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/record_language_bottom_sheet.dart @@ -14,17 +14,17 @@ class RecordLanguageBottomSheet extends StatefulWidget { class _RecordLanguageBottomSheetState extends State { final List _languages = [ - {'name': '中文普通话(简体)', 'value': 'zh-CN'}, - {'name': '粤语', 'value': 'cant'}, - {'name': '四川话', 'value': 'sc'}, - {'name': '上海话', 'value': 'zh_shanghai'}, - {'name': '英文', 'value': 'en-US'}, - {'name': '日语', 'value': 'ja-JP'}, - {'name': '韩语', 'value': 'ko-KR'}, - {'name': '法语', 'value': 'fr-FR'}, - {'name': '西班牙语', 'value': 'es-MX'}, - {'name': '葡萄牙语', 'value': 'pt-BR'}, - {'name': '印尼语', 'value': 'id-ID'}, + {'name': 'chinese'.tr, 'value': 'zh-CN'}, // 对应中文:中文普通话(简体) + {'name': 'cantonese'.tr, 'value': 'cant'}, // 对应中文:粤语 + {'name': 'sichuan'.tr, 'value': 'sc'}, // 对应中文:四川话 + {'name': 'shanghai'.tr, 'value': 'zh_shanghai'}, // 对应中文:上海话 + {'name': 'english'.tr, 'value': 'en-US'}, // 对应中文:英文 + {'name': 'japanese'.tr, 'value': 'ja-JP'}, // 对应中文:日语 + {'name': 'korean'.tr, 'value': 'ko-KR'}, // 对应中文:韩语 + {'name': 'french'.tr, 'value': 'fr-FR'}, // 对应中文:法语 + {'name': 'spanish'.tr, 'value': 'es-MX'}, // 对应中文:西班牙语 + {'name': 'portuguese'.tr, 'value': 'pt-BR'}, // 对应中文:葡萄牙语 + {'name': 'indonesian'.tr, 'value': 'id-ID'}, // 对应中文:印尼语 ]; @override @@ -40,7 +40,7 @@ class _RecordLanguageBottomSheetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '录音语言', + 'recordLanguage'.tr, // 对应中文:录音语言 style: TextStyle( fontSize: 16.sp, color: Colors.black87, diff --git a/lib/modules/meeting/views/bottomSheet/second_level_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/second_level_bottom_sheet.dart index 023056133..8bcfc410a 100644 --- a/lib/modules/meeting/views/bottomSheet/second_level_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/second_level_bottom_sheet.dart @@ -148,7 +148,7 @@ class _SecondLevelBottomSheetState extends State { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '导出', + 'export'.tr, // 对应中文:导出 style: TextStyle( fontSize: 16.sp, color: Colors.white, diff --git a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart index 55086266f..66afed3ec 100644 --- a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart @@ -79,7 +79,7 @@ class _ShareBottomSheetState extends State { _box([ _shareItem( Icons.link, - '分享链接', + 'shareLink'.tr, // 对应中文:分享链接 isBorder: false, onTap: () async { Get.back(); @@ -110,20 +110,20 @@ class _ShareBottomSheetState extends State { previewThumbnail: thumbnail, )); if (result.status == ShareResultStatus.success) { - EasyLoading.showToast('分享完成!'); + EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成 } }, ), ]), _box([ - _shareItem(Icons.copy, '复制转写', + _shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写 onTap: _controller.meetingDetails.value.tasktype >= 2 ? () { Get.back(); _copy(_getAllText()); } : null), - _shareItem(Icons.copy, '复制总结', + _shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结 isBorder: false, onTap: _controller.meetingDetails.value.tasktype >= 3 ? () { @@ -133,11 +133,14 @@ class _ShareBottomSheetState extends State { : null), ]), _box([ - _shareItem(Icons.multitrack_audio, '导出音频', onTap: () { + _shareItem( + Icons.multitrack_audio, 'exportAudio'.tr, // 对应中文:导出音频 + onTap: () { 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 ? () async { Get.back(); @@ -146,17 +149,18 @@ class _ShareBottomSheetState extends State { '${tempDir.path}/${_controller.meetingData.value.title}_speaker.txt'); String allText = _getAllText(); await file.writeAsString(allText); - _share(file.path, '分享会议转写'); + _share(file.path, + 'exportTranscript'.tr); // 对应中文:分享会议转写 } : null), - _shareItem(Icons.article, '导出总结', + _shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结 onTap: _controller.meetingDetails.value.tasktype >= 3 ? () async { Get.back(); Get.bottomSheet( SecondLevelBottomSheet( - '导出总结', - '导出为', + 'exportSummary'.tr, // 对应中文:导出总结 + 'exportAs'.tr, // 对应中文:导出为 (int index) async { if (index == 0) { // 导出TXT @@ -166,7 +170,8 @@ class _ShareBottomSheetState extends State { '${tempDir.path}/${_controller.meetingData.value.title}_summary.txt'); await file.writeAsString(_controller .meetingDetails.value.summary); - _share(file.path, '分享会议总结'); + _share(file.path, + 'exportSummary'.tr); // 对应中文:分享会议总结 } else { // 导出PDF _exportMarkdownPdf(); @@ -197,7 +202,7 @@ class _ShareBottomSheetState extends State { : null), _shareItem( Icons.lan, - '导出思维导图', + 'shareMindMap'.tr, // 对应中文:分享思维导图 isBorder: false, onTap: _controller.meetingDetails.value.tasktype >= 3 ? () { @@ -298,7 +303,7 @@ class _ShareBottomSheetState extends State { files: [XFile(filepath)], )); if (result.status == ShareResultStatus.success) { - EasyLoading.showToast('分享完成!'); + EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成! } } @@ -307,8 +312,8 @@ class _ShareBottomSheetState extends State { text: text, )).then( (value) => Get.snackbar( - 'success'.tr, // 成功 - '复制成功', + 'success'.tr, // 对应中文:成功 + 'copySuccess'.tr, // 对应中文:复制成功 backgroundColor: Colors.blue.withValues(alpha: 0.8), colorText: Colors.white, ), @@ -340,7 +345,7 @@ class _ShareBottomSheetState extends State { } Future _exportMarkdownPdf() async { - EasyLoading.show(status: '正在导出PDF...'); + EasyLoading.show(status: 'exportingPDF'.tr); // 对应中文:正在导出PDF... final tempDir = await getTemporaryDirectory(); final file = File( '${tempDir.path}/${_controller.meetingData.value.title}_summary.pdf'); @@ -357,7 +362,7 @@ class _ShareBottomSheetState extends State { build: (context) => markdownWidgets, )); await file.writeAsBytes(await markdownPdf.save()); - await _share(file.path, '分享会议总结'); + await _share(file.path, 'exportSummary'.tr); // 对应中文:分享会议总结 EasyLoading.dismiss(); } } diff --git a/lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart index 07ba347b0..ba1708b90 100644 --- a/lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/speaker_all_bottom_sheet.dart @@ -100,7 +100,7 @@ class _SpeakerAllBottomSheetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '命名发言者', + 'nameSpeaker'.tr, // 对应中文:命名发言者 style: TextStyle( fontSize: 16.sp, color: Colors.black87, @@ -209,7 +209,7 @@ class _SpeakerAllBottomSheetState extends State { Padding( padding: EdgeInsets.only(top: 10.w, bottom: 5.w), child: Text( - '最近使用过的名称', + 'recentlyUsedNames'.tr, // 对应中文:最近使用过的名称 style: TextStyle( fontSize: 12.sp, color: Colors.grey[600]!, @@ -259,7 +259,7 @@ class _SpeakerAllBottomSheetState extends State { child: Row( children: [ Text( - '总计', + 'totalTime'.tr, // 对应中文:总计 style: TextStyle( fontSize: 12.sp, color: Colors.grey, @@ -431,7 +431,7 @@ class _SpeakerAllBottomSheetState extends State { Padding( padding: EdgeInsets.symmetric(vertical: 5.w), child: Text( - '总结的发言者将自动更新', + 'summarySpeakerWillBeUpdatedAutomatically'.tr, // 对应中文:总结的发言者将自动更新 style: TextStyle( fontSize: 14.sp, color: Colors.grey[600], @@ -453,7 +453,7 @@ class _SpeakerAllBottomSheetState extends State { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '取消', + 'cancel'.tr, // 对应中文:取消 style: TextStyle( fontSize: 16.sp, color: Colors.black, @@ -474,7 +474,8 @@ class _SpeakerAllBottomSheetState extends State { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '确认', + 'confirm'.tr, // 对应中文:确认 + style: TextStyle( fontSize: 16.sp, color: Colors.white, diff --git a/lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart index 3c7e69dad..a93f9fa31 100644 --- a/lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/speaker_bottom_sheet.dart @@ -71,7 +71,7 @@ class _SpeakerBottomSheetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '命名当前发言者', + 'nameCurrentSpeaker'.tr, // 对应中文:命名当前发言者 style: TextStyle( fontSize: 16.sp, color: Colors.black87, @@ -158,7 +158,7 @@ class _SpeakerBottomSheetState extends State { children: [ if (index == 0) Text( - '最近使用过的名称', + 'recentlyUsedNames'.tr, // 对应中文:最近使用过的名称 style: TextStyle( fontSize: 12.sp, color: Colors.grey[600]!, @@ -189,15 +189,18 @@ class _SpeakerBottomSheetState extends State { }, ), ), - _row('应用到当前段落', _type == 0, onTap: () { + _row( + 'applyToCurrentParagraph'.tr, // 对应中文:应用到当前段落 + _type == 0, onTap: () { setState(() { _type = 0; }); }), _row( - '应用到这个发言者的所有段落', + 'applyToAllParagraphs'.tr, // 对应中文:应用到这个发言者的所有段落 _type == 1, - description: '总结将根据模版自动更新', + description: + 'summaryWillBeUpdatedAutomatically'.tr, // 对应中文:总结将根据模版自动更新 onTap: () { setState(() { _type = 1; @@ -216,7 +219,7 @@ class _SpeakerBottomSheetState extends State { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '保存', + 'save'.tr, // 对应中文:保存 style: TextStyle( fontSize: 16.sp, color: Colors.white, diff --git a/lib/modules/meeting/views/meeting_connect_view.dart b/lib/modules/meeting/views/meeting_connect_view.dart index e56d0ab1f..c056c2c4d 100644 --- a/lib/modules/meeting/views/meeting_connect_view.dart +++ b/lib/modules/meeting/views/meeting_connect_view.dart @@ -32,7 +32,7 @@ class MeetingConnectView extends GetView { onPressed: () => Get.back(), ), title: Text( - '蓝牙连接', + 'bluetoothConnection'.tr, // 对应中文:蓝牙连接 style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, @@ -63,7 +63,7 @@ class MeetingConnectView extends GetView { ), Obx( () => controller.isRescan.value - ? _button('重新连接', Icons.refresh, onTap: () { + ? _button('meetingReconnect'.tr, Icons.refresh, onTap: () { // 对应中文:重新连接 controller.startScan(); }) : const SizedBox(), diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart index 0d8ae4769..15444981a 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -301,10 +301,10 @@ class MeetingDetailsView extends GetView { child: Obx( () => Row( children: [ - _iconItem(Icons.summarize, '概览', 0), - _iconItem(Icons.message, '转写', 1), - _iconItem(Icons.article, '总结', 2), - _iconItem(Icons.lan, '思维导图', 3), + _iconItem(Icons.summarize, 'overview'.tr, 0), // 对应中文:概览 + _iconItem(Icons.message, 'transcription'.tr, 1), // 对应中文:转写 + _iconItem(Icons.article, 'summary'.tr, 2), // 对应中文:总结 + _iconItem(Icons.lan, 'mindMap'.tr, 3), // 对应中文:思维导图 ], ), ), @@ -327,12 +327,12 @@ class MeetingDetailsView extends GetView { ? const SizedBox() : controller.meetingDetails.value.tasktype == 1 || controller.meetingDetails.value.tasktype == 2 - ? _meetingWait('正在分析...') + ? _meetingWait('analyzing'.tr) // 对应中文:正在分析... : const OverviewTab(), ), Obx( () => controller.meetingDetails.value.tasktype == 1 - ? _meetingWait('正在转写...') + ? _meetingWait('transcribing'.tr) // 对应中文:正在转写... : const SpeechTab(), ), Obx( @@ -340,8 +340,8 @@ class MeetingDetailsView extends GetView { controller.meetingDetails.value.tasktype == 2 ? _meetingWait( controller.meetingDetails.value.tasktype == 1 - ? '正在转写...' - : '正在总结...') + ? 'transcribing'.tr // 对应中文:正在转写... + : 'summarizing'.tr) // 对应中文:正在总结... : const SummaryTab(), ), Obx( @@ -351,8 +351,8 @@ class MeetingDetailsView extends GetView { ? const SizedBox() : _meetingWait( controller.meetingDetails.value.tasktype == 1 - ? '正在转写...' - : '正在总结...'), + ? 'transcribing'.tr // 对应中文:正在转写... + : 'summarizing'.tr), // 对应中文:正在总结... ), ], ), diff --git a/lib/modules/meeting/views/meeting_home_view.dart b/lib/modules/meeting/views/meeting_home_view.dart index 6a6e1ec5f..f1c9ed0f8 100644 --- a/lib/modules/meeting/views/meeting_home_view.dart +++ b/lib/modules/meeting/views/meeting_home_view.dart @@ -86,7 +86,7 @@ class _MeetingHomeViewState extends State 5.horizontalSpace, Obx( () => Text( - '${_controller.filtrateType.value.isNotEmpty ? _controller.filtrateType : '全部文件'}(${_controller.dataList.length})', + '${_controller.filtrateType.value.isNotEmpty ? _controller.filtrateType : 'allFiles'.tr}(${_controller.dataList.length})', // 对应中文:全部文件 style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, fontSize: 16.sp, @@ -136,7 +136,7 @@ class _MeetingHomeViewState extends State Padding( padding: EdgeInsets.symmetric(horizontal: 5.w), child: Text( - '连接', + 'connect'.tr, // 对应中文:连接 style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, fontSize: 14.sp, @@ -329,10 +329,10 @@ class _MeetingHomeViewState extends State padding: EdgeInsets.only(top: 5.w), child: Text( taskData['tasktype'] == 1 - ? '正在转写...' + ? 'transcribing'.tr // 对应中文:正在转写... : taskData['tasktype'] == 2 - ? '正在总结...' - : '总结完成', + ? 'summarizing'.tr // 对应中文:正在总结... + : 'summaryComplete'.tr, // 对应中文:总结完成 style: TextStyle( color: Colors.blue, fontSize: 10.sp, diff --git a/lib/modules/meeting/views/meeting_mine_view.dart b/lib/modules/meeting/views/meeting_mine_view.dart index 9a6c8363c..42291a6a0 100644 --- a/lib/modules/meeting/views/meeting_mine_view.dart +++ b/lib/modules/meeting/views/meeting_mine_view.dart @@ -54,7 +54,7 @@ class _MeetingMineViewState extends State _box([ _item( Icons.article_outlined, - '转写记录', + 'transcriptionRecords'.tr, // 对应中文:转写记录 onTap: () { Get.to(() => const MeetingTranscriptionView()); }, @@ -63,14 +63,14 @@ class _MeetingMineViewState extends State _box([ _item( Icons.layers_outlined, - '模版社区', - description: '尽请期待', + 'templateCommunity'.tr, // 对应中文:模版社区 + description: 'comingSoon'.tr, // 对应中文:尽请期待 onTap: () {}, ), _item( Icons.sync_alt_outlined, - '集成', - description: '尽请期待', + 'integration'.tr, // 对应中文:集成 + description: 'comingSoon'.tr, // 对应中文:尽请期待 onTap: () {}, ), ]), @@ -80,8 +80,8 @@ class _MeetingMineViewState extends State Icons.cloud_upload_outlined, 'VOITRANS PRIVATE CLOUD', description: _meetingController.user['privatecloud'] == 1 - ? '开启' - : '关闭', + ? 'enabled'.tr // 对应中文:开启 + : 'disabled'.tr, // 对应中文:关闭 onTap: () { Get.to(() => const MeetingCloudView()); }, @@ -97,7 +97,7 @@ class _MeetingMineViewState extends State ), _item( Icons.settings_remote_outlined, - '我的VOITRANS设备', + 'myVoitransDevice'.tr, // 对应中文:我的VOITRANS设备 onTap: () { Get.toNamed(Routes.devices); }, @@ -106,14 +106,14 @@ class _MeetingMineViewState extends State _box([ _item( Icons.help_outline_rounded, - '帮助与反馈', + 'helpAndFeedback'.tr, // 对应中文:帮助与反馈 onTap: () { Get.toNamed(Routes.feedback); }, ), _item( Icons.info_outline_rounded, - '关于VOITRANS', + 'aboutVoitrans'.tr, // 对应中文:关于VOITRANS description: 'v1.0.0', onTap: () {}, ), @@ -189,17 +189,17 @@ class _MeetingMineViewState extends State children: [ _statisticsItem( '${_controller.days.value}', - '天', + 'meetingDays'.tr, // 对应中文:天 Colors.lime, ), _statisticsItem( '${_controller.numberFiles.value}', - '文件', + 'files'.tr, // 对应中文:文件 Colors.lightGreen, ), _statisticsItem( '${_controller.hourage.value}', - '小时', + 'meetingHours'.tr, // 对应中文:小时 Colors.blueGrey, ), ], @@ -305,7 +305,7 @@ class _MeetingMineViewState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Voitrans AI会员', + 'voitransAiMember'.tr, // 对应中文:Voitrans AI会员 style: TextStyle( color: MeetingUIUtils.getTextColor(isDarkMode), fontSize: 16.sp, @@ -350,7 +350,7 @@ class _MeetingMineViewState extends State ), 5.horizontalSpace, Text( - '解锁 Starter 会员', + 'unlockStarterMember'.tr, // 对应中文:解锁 Starter 会员 style: TextStyle( color: Colors.white, fontSize: 18.sp, @@ -365,7 +365,7 @@ class _MeetingMineViewState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '绑定 Voitrans 设备可激活 Starter 会员', + 'bindDeviceToActivate'.tr, // 对应中文:绑定 Voitrans 设备可激活 Starter 会员 style: TextStyle( color: Colors.white, fontSize: 12.sp, @@ -385,7 +385,7 @@ class _MeetingMineViewState extends State ), ), child: Text( - '立即解锁', + 'unlockNow'.tr, // 对应中文:立即解锁 style: TextStyle( color: Colors.white, fontSize: 14.sp, diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart index 6ddb25a75..5f453d387 100644 --- a/lib/modules/meeting/views/meeting_record_view.dart +++ b/lib/modules/meeting/views/meeting_record_view.dart @@ -347,7 +347,7 @@ class MeetingRecordView extends GetView { // 标题 Text( - 'editMeetingTitle'.tr, + 'editMeetingTitle'.tr, // 对应中文:修改会议标题(非必填) style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, fontSize: 20.sp, diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart index dfa9a4e5a..a2aa30687 100644 --- a/lib/modules/meeting/views/meeting_view.dart +++ b/lib/modules/meeting/views/meeting_view.dart @@ -35,11 +35,11 @@ class MeetingView extends GetView { right: 0, child: Obx( () => BottomNavigationBar( - items: const [ + items: [ BottomNavigationBarItem( icon: Icon(Icons.folder_outlined), activeIcon: Icon(Icons.folder), - label: '文件', + label: 'files'.tr, // 对应中文:文件 ), BottomNavigationBarItem( icon: SizedBox(), @@ -48,7 +48,7 @@ class MeetingView extends GetView { BottomNavigationBarItem( icon: Icon(Icons.person_outline), activeIcon: Icon(Icons.person), - label: '我的', + label: 'mine'.tr, // 对应中文:我的 ), ], backgroundColor: diff --git a/lib/modules/meeting/views/mine/meeting_cloud_view.dart b/lib/modules/meeting/views/mine/meeting_cloud_view.dart index 7f9e0d41f..e209f5b78 100644 --- a/lib/modules/meeting/views/mine/meeting_cloud_view.dart +++ b/lib/modules/meeting/views/mine/meeting_cloud_view.dart @@ -60,7 +60,7 @@ class _MeetingCloudViewState extends State { onPressed: () => Get.back(), ), title: Text( - '同步到云端', + 'syncToCloud'.tr, // 对应中文:同步到云端 style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, @@ -101,7 +101,7 @@ class _MeetingCloudViewState extends State { Padding( padding: EdgeInsets.only(top: 20.w, bottom: 10.w), child: Text( - '同步到云端', + 'syncToCloud'.tr, // 对应中文:同步到云端 style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, fontSize: 18.sp, @@ -110,7 +110,8 @@ class _MeetingCloudViewState extends State { ), ), Text( - '自动将你的录音同步到云端,你可以通过任意VOITRANS APP或VOITRANS Web(web.voitrans.com)播放、管理和分享录音。', + 'syncToCloudTip' + .tr, // 对应中文:自动将你的录音同步到云端,你可以通过任意VOITRANS APP或VOITRANS Web(web.voitrans.com)播放、管理和分享录音。 textAlign: TextAlign.center, style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, @@ -132,7 +133,7 @@ class _MeetingCloudViewState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '同步VOITRANS APP', + 'syncToVoitransApp'.tr, // 对应中文:同步VOITRANS APP style: TextStyle( color: isDarkMode ? Colors.white : Colors.black87, fontSize: 16.sp, diff --git a/lib/modules/meeting/views/mine/meeting_member_view.dart b/lib/modules/meeting/views/mine/meeting_member_view.dart index 89bfdd27d..29875e100 100644 --- a/lib/modules/meeting/views/mine/meeting_member_view.dart +++ b/lib/modules/meeting/views/mine/meeting_member_view.dart @@ -43,7 +43,7 @@ class _MeetingMemberViewState extends State { onPressed: () => Get.back(), ), title: Text( - 'VOITRANS AI会员', + 'voitransAiMember'.tr, // 对应中文:VOITRANS AI会员 style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, @@ -133,7 +133,7 @@ class _MeetingMemberViewState extends State { ), 5.horizontalSpace, Text( - '解锁 Starter 会员', + 'unlockStarterMember'.tr, // 对应中文:解锁 Starter 会员 style: TextStyle( color: Colors.white, fontSize: 18.sp, @@ -148,7 +148,7 @@ class _MeetingMemberViewState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '绑定 Voitrans 设备可激活 Starter 会员', + 'bindDeviceToActivate'.tr, // 对应中文:绑定 Voitrans 设备可激活 Starter 会员 style: TextStyle( color: Colors.white, fontSize: 12.sp, @@ -168,7 +168,7 @@ class _MeetingMemberViewState extends State { ), ), child: Text( - '立即解锁', + 'unlockNow'.tr, // 对应中文:立即解锁 style: TextStyle( color: Colors.white, fontSize: 14.sp, @@ -213,7 +213,7 @@ class _MeetingMemberViewState extends State { ) : null, child: Text( - '对比', + 'comparison'.tr, // 对应中文:对比 style: TextStyle( color: _tabsIndex == 0 ? Colors.black : Colors.white, fontSize: 14.sp, @@ -241,7 +241,7 @@ class _MeetingMemberViewState extends State { ) : null, child: Text( - '详细信息', + 'detailedInfo'.tr, // 对应中文:详细信息 style: TextStyle( color: _tabsIndex == 1 ? Colors.black : Colors.white, fontSize: 14.sp, @@ -342,24 +342,24 @@ class _MeetingMemberViewState extends State { ), ), _tableItem( - '每月转写时长', + 'monthlyTranscriptionTime'.tr, // 对应中文:每月转写时长 starter: '300', pro: '1200', - unlimited: '无限', + unlimited: 'unlimited'.tr, // 对应中文:无限 isGradient: true, ), _tableItem( - '专业模版', - starter: '有限', - pro: '全量模版', - unlimited: '全量模版', + 'professionalTemplates'.tr, // 对应中文:专业模版 + starter: 'limited'.tr, // 对应中文:有限 + pro: 'fullTemplates'.tr, // 对应中文:全量模版 + unlimited: 'fullTemplates'.tr, // 对应中文:全量模版 ), _tableItem( - '专业术语', + 'professionalTerms'.tr, // 对应中文:专业术语 starter: '—', ), _tableItem( - '自定义模版', + 'customTemplates'.tr, // 对应中文:自定义模版 starter: '—', ), _tableItem( @@ -367,22 +367,22 @@ class _MeetingMemberViewState extends State { starter: '—', ), _tableItem( - '可视化思维导图', + 'visualMindMap'.tr, // 对应中文:可视化思维导图 ), _tableItem( - '区分说话人', + 'speakerIdentification'.tr, // 对应中文:区分说话人 ), _tableItem( 'Private Cloud Sync', ), _tableItem( - 'AI 人声增强', + 'aiVoiceEnhancement'.tr, // 对应中文:AI 人声增强 ), _tableItem( - '音频导入', + 'audioImport'.tr, // 对应中文:音频导入 ), _tableItem( - '支持多种导出格式', + 'multipleExportFormats'.tr, // 对应中文:支持多种导出格式 ), ], ); @@ -394,62 +394,62 @@ class _MeetingMemberViewState extends State { _detailedItem( Icons.description, Colors.purpleAccent, - '专业总结模版', - '专业模版设计,提高您的工作效率。根据行业需求量身定制。', + 'professionalSummaryTemplates'.tr, // 对应中文:专业总结模版 + 'professionalTemplateDesc'.tr, // 对应中文:专业模版设计,提高您的工作效率。根据行业需求量身定制。 ), _detailedItem( Icons.edit_note, Colors.purpleAccent, - '自定义模版', - '您可以根据需求自定义总结 Prompts,并用它们生成所需的总结。', + 'customTemplates'.tr, // 对应中文:自定义模版 + 'customTemplateDesc'.tr, // 对应中文:您可以根据需求自定义总结 Prompts,并用它们生成所需的总结。 ), _detailedItem( Icons.psychology, Colors.purpleAccent, 'Ask AI', - '从录音中智能挖掘更深入信息,一键生成数据表格、关键讨论结论、To Do、汇报邮件等格式内容。', + 'askAiDesc'.tr, // 对应中文:从录音中智能挖掘更深入信息,一键生成数据表格、关键讨论结论、To Do、汇报邮件等格式内容。 ), _detailedItem( Icons.account_tree, Colors.purpleAccent, - '可视化思维导图', - '超越总结-用思维导图整理和可视化要点,更有效地理解全文。', + 'visualMindMap'.tr, // 对应中文:可视化思维导图 + 'visualMindMapDesc'.tr, // 对应中文:超越总结-用思维导图整理和可视化要点,更有效地理解全文。 ), _detailedItem( Icons.groups, Colors.purpleAccent, - '模版社区', - '发现、分享,并从用户创建的模版库中获得灵感。', + 'templateCommunity'.tr, // 对应中文:模版社区 + 'templateCommunityDesc'.tr, // 对应中文:发现、分享,并从用户创建的模版库中获得灵感。 ), _detailedItem( Icons.people, Colors.red, - '区分说话人', - '音频转写将支持区分发言者,更直观的还原会议或电话通话内容。同时总结将更加准确。', + 'speakerIdentification'.tr, // 对应中文:区分说话人 + 'speakerIdentificationDesc'.tr, // 对应中文:音频转写将支持区分发言者,更直观的还原会议或电话通话内容。同时总结将更加准确。 ), _detailedItem( Icons.cloud_sync, Colors.red, 'Private Cloud Sync', - '您的录音和转写等数据将安全存储在专属的云端服务器中,无论是上传还是下载,全程加密处理,确保隐私安全。', + 'privateCloudSyncDesc'.tr, // 对应中文:您的录音和转写等数据将安全存储在专属的云端服务器中,无论是上传还是下载,全程加密处理,确保隐私安全。 ), _detailedItem( Icons.graphic_eq, Colors.red, - 'AI人声增强', - 'Al 人声增强支持多档降噪设置,智能消除背景噪音,增强语音清晰度,提升音频回放效果。', + 'aiVoiceEnhancement'.tr, // 对应中文:AI人声增强 + 'aiVoiceEnhancementDesc'.tr, // 对应中文:Al 人声增强支持多档降噪设置,智能消除背景噪音,增强语音清晰度,提升音频回放效果。 ), _detailedItem( Icons.file_upload, Colors.red, - '音频导入', - '可以轻松从本地或第三方应用导入音频文件,如语音备忘录、Google云端硬盘等。', + 'audioImport'.tr, // 对应中文:音频导入 + 'audioImportDesc'.tr, // 对应中文:可以轻松从本地或第三方应用导入音频文件,如语音备忘录、Google云端硬盘等。 ), _detailedItem( Icons.file_download, Colors.blue, - '多种导出格式', - '导出您的音频录音、转写文本和摘要为各种格式(TXT、PDF、JPEG),以满足您的需求。', + 'multipleExportFormats'.tr, // 对应中文:多种导出格式 + 'multipleExportFormatsDesc'.tr, // 对应中文:导出您的音频录音、转写文本和摘要为各种格式(TXT、PDF、JPEG),以满足您的需求。 ), ], ); @@ -644,7 +644,7 @@ class _MeetingMemberViewState extends State { margin: EdgeInsets.only(top: 30.w), alignment: Alignment.center, child: Text( - '升级您的会员', + 'upgradeMembership'.tr, // 对应中文:升级您的会员 style: TextStyle( color: Colors.white, fontSize: 24.sp, @@ -675,7 +675,7 @@ class _MeetingMemberViewState extends State { ) : null, child: Text( - '年订阅', + 'yearlySubscription'.tr, // 对应中文:年订阅 style: TextStyle( color: Colors.white, fontSize: 16.sp, @@ -701,7 +701,7 @@ class _MeetingMemberViewState extends State { ) : null, child: Text( - '月订阅', + 'monthlySubscription'.tr, // 对应中文:月订阅 style: TextStyle( color: Colors.white, fontSize: 16.sp, @@ -714,9 +714,9 @@ class _MeetingMemberViewState extends State { ), if (_upgradeIndex == 0) _upgradeItem( - 'Pro ¥60/月', - '7天免费试用,之后按¥800/年。试用期结束前可随时取消。', - '7天免费试用', + 'proMonthly'.tr, // 对应中文:Pro ¥60/月 + 'proMonthlyDesc'.tr, // 对应中文:7天免费试用,之后按¥800/年。试用期结束前可随时取消。 + 'sevenDayTrial'.tr, // 对应中文:7天免费试用 onTap: () {}, ), if (_upgradeIndex == 0) diff --git a/lib/modules/meeting/views/mine/meeting_transcription_view.dart b/lib/modules/meeting/views/mine/meeting_transcription_view.dart index 3a4653f62..fca4c5e50 100644 --- a/lib/modules/meeting/views/mine/meeting_transcription_view.dart +++ b/lib/modules/meeting/views/mine/meeting_transcription_view.dart @@ -37,7 +37,7 @@ class MeetingTranscriptionView extends GetView { onPressed: () => Get.back(), ), title: Text( - '转写记录', + 'transcriptionRecord'.tr, // 对应中文:转写记录 style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, diff --git a/lib/modules/meeting/views/tabs/overview_tab.dart b/lib/modules/meeting/views/tabs/overview_tab.dart index ca5f016bb..5d58d42ab 100644 --- a/lib/modules/meeting/views/tabs/overview_tab.dart +++ b/lib/modules/meeting/views/tabs/overview_tab.dart @@ -71,7 +71,7 @@ class _OverviewTabState extends State { Padding( padding: EdgeInsets.symmetric(vertical: 10.w), child: Text( - '分析出${_dataList.length}个关键事件。', + 'keyEventsAnalyzed'.trParams({'count': '${_dataList.length}'}), // 对应中文:分析出{count}个关键事件。 style: TextStyle( color: isDarkMode ? Colors.grey[400] : Colors.grey, fontSize: 12.sp, @@ -120,7 +120,7 @@ class _OverviewTabState extends State { width: 150.w, child: _button( Icons.auto_awesome, - '生成', + 'generate'.tr, // 对应中文:生成 () { Get.bottomSheet( const GenerateBottomSheet(), @@ -210,7 +210,7 @@ class _OverviewTabState extends State { ), ), Text( - '概览', + 'overview'.tr, // 对应中文:概览 style: TextStyle( color: MeetingUIUtils.getTextColor(isDarkMode), fontSize: 18.sp, @@ -218,7 +218,7 @@ class _OverviewTabState extends State { ), ), Text( - 'AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。', + 'aiAnalysisDesc'.tr, // 对应中文:AI分析识别录音中的关键事件,让你选择重要事件进行AI生成。 style: TextStyle( color: MeetingUIUtils.getSecondaryTextColor(isDarkMode), fontSize: 14.sp, diff --git a/lib/modules/meeting/views/tabs/speech_tab.dart b/lib/modules/meeting/views/tabs/speech_tab.dart index d0f2fa9ea..b42d9af49 100644 --- a/lib/modules/meeting/views/tabs/speech_tab.dart +++ b/lib/modules/meeting/views/tabs/speech_tab.dart @@ -256,7 +256,7 @@ class SpeechTab extends GetView { !controller.isSpeakerText.value) _button( Icons.edit, - '编辑', + 'editSpeaker'.tr, // 对应中文:编辑 () { controller.isSpeakerText.value = true; controller.dragOffset.value = 0; @@ -267,7 +267,7 @@ class SpeechTab extends GetView { !controller.isSpeakerText.value) _button( Icons.supervisor_account, - '命名发言者', + 'nameSpeaker'.tr, // 对应中文:命名发言者 () { Get.bottomSheet( const SpeakerAllBottomSheet(), @@ -304,7 +304,7 @@ class SpeechTab extends GetView { width: 150.w, child: _button( Icons.auto_awesome, - '生成', + 'generate'.tr, // 对应中文:生成 () { Get.bottomSheet( const GenerateBottomSheet(), diff --git a/lib/modules/meeting/views/tabs/summary_tab.dart b/lib/modules/meeting/views/tabs/summary_tab.dart index bd24e8c4f..5863fe14b 100644 --- a/lib/modules/meeting/views/tabs/summary_tab.dart +++ b/lib/modules/meeting/views/tabs/summary_tab.dart @@ -23,9 +23,9 @@ class _SummaryTabState extends State { late final Worker _personnelListener; - final String _addressPrefix = '地点:'; + final String _addressPrefix = '${'location'.tr}:'; // 对应中文:地点 String _addressText = ''; - final String _personnelPrefix = '与会人员:'; + final String _personnelPrefix = '${'attendees'.tr}:'; // 对应中文:与会人员 String _personnelText = ''; final TextEditingController _addressController = TextEditingController(); final TextEditingController _personnelController = TextEditingController(); @@ -57,11 +57,11 @@ class _SummaryTabState extends State { _addressController.text = _addressPrefix + (_controller.meetingDetails.value.address.isNotEmpty ? _controller.meetingDetails.value.address - : '[输入地点]'); + : '[${'inputLocation'.tr}]'); // 对应中文:输入地点 _personnelController.text = _personnelPrefix + (_controller.meetingDetails.value.personnel.isNotEmpty ? _controller.meetingDetails.value.personnel - : '[输入与会人员]'); + : '[${'inputAttendees'.tr}]'); // 对应中文:输入与会人员 final markdown = const ParchmentMarkdownCodec() .decode(_controller.meetingDetails.value.summary); setState(() { @@ -288,7 +288,7 @@ class _SummaryTabState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '日期和时间:${DateUtil.formatDateMs( + '${'dateAndTime'.tr}:${DateUtil.formatDateMs( _controller.meetingData.value.creationtime, format: DateFormats.y_mo_d_h_m, )}', @@ -346,12 +346,13 @@ class _SummaryTabState extends State { if (_controller.meetingDetails.value.tasktype >= 3) _button( Icons.supervisor_account, - '编辑发言者', + 'editSpeaker'.tr, // 对应中文:编辑发言者 () { Get.bottomSheet( const SpeakerAllBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), @@ -368,7 +369,8 @@ class _SummaryTabState extends State { Get.bottomSheet( const AIBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.white, + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), @@ -382,12 +384,14 @@ class _SummaryTabState extends State { width: 150.w, child: _button( Icons.auto_awesome, - '生成', + 'generate'.tr, // 对应中文:生成 () { Get.bottomSheet( const GenerateBottomSheet(), isScrollControlled: true, - backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100], + backgroundColor: isDarkMode + ? Colors.grey[800] + : Colors.grey[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12.r), diff --git a/lib/modules/meeting/views/template/template_view.dart b/lib/modules/meeting/views/template/template_view.dart index a1cccd0ff..2d77797fb 100644 --- a/lib/modules/meeting/views/template/template_view.dart +++ b/lib/modules/meeting/views/template/template_view.dart @@ -97,7 +97,7 @@ class _TemplateViewState extends State { onPressed: () => Get.back(), ), title: Text( - '选择总结模版', + 'selectSummaryTemplate'.tr, // 对应中文:选择总结模版 style: TextStyle( fontSize: 17.sp, fontWeight: FontWeight.w600, @@ -135,7 +135,7 @@ class _TemplateViewState extends State { borderRadius: BorderRadius.circular(12.r), ), child: Text( - '就用这个模版', + 'useThisTemplate'.tr, // 对应中文:就用这个模版 style: TextStyle( fontSize: 16.sp, color: Colors.white, @@ -304,7 +304,7 @@ class _TemplateViewState extends State { ), ), child: Text( - '上次使用', + 'lastUsed'.tr, // 对应中文:上次使用 style: TextStyle( color: Colors.white, fontSize: 12.sp, diff --git a/lib/modules/permissions/controllers/permissions_controller.dart b/lib/modules/permissions/controllers/permissions_controller.dart index 1c40d55a2..73ac1b605 100644 --- a/lib/modules/permissions/controllers/permissions_controller.dart +++ b/lib/modules/permissions/controllers/permissions_controller.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter/material.dart'; import 'dart:io' show Platform; @@ -16,9 +17,13 @@ class PermissionsController extends GetxController { // Android版本信息(用于权限组显示判断) final RxInt androidSdkVersion = 0.obs; + final GetStorage _storage = GetStorage(); + bool? _isCalendarFullAccess; + @override void onInit() { super.onInit(); + _isCalendarFullAccess = _storage.read('record_permission_calendar'); _initAndroidVersion(); checkAllPermissions(); } @@ -68,7 +73,19 @@ class PermissionsController extends GetxController { for (final permission in permissions) { 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]}'); } catch (e) { // 某些权限在特定平台可能不支持,设置为拒绝状态 @@ -171,6 +188,12 @@ class PermissionsController extends GetxController { if (hasPermission) { _showSuccessMessage(); + if (Platform.isIOS && permission == Permission.calendarFullAccess) { + if (_isCalendarFullAccess == null) { + _storage.write('record_permission_calendar', true); + _isCalendarFullAccess = true; + } + } } // 刷新权限状态 diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 4220460bd..c626f2e54 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -515,27 +515,27 @@ class SettingsView extends GetView { // ? Colors.white.withOpacity(0.1) // : Colors.grey[200]), //BLE测试 - // _buildSimpleNavigationSetting( - // title: 'opus解码测试', - // subtitle: '测试opus解码', - // icon: Icons.bluetooth_searching, - // iconBgColor: isDarkMode - // ? Colors.green[900]!.withOpacity(0.3) - // : Colors.green[100]!, - // iconColor: - // isDarkMode ? Colors.green[300]! : Colors.green[600]!, - // titleColor: isDarkMode ? Colors.white : null, - // subtitleColor: isDarkMode ? Colors.white70 : null, - // onTap: () { - // Get.toNamed(Routes.opusTest); - // }, - // isDarkMode: isDarkMode, - // ), - // Divider( - // height: 1, - // color: isDarkMode - // ? Colors.white.withOpacity(0.1) - // : Colors.grey[200]), + _buildSimpleNavigationSetting( + title: 'opus解码测试', + subtitle: '测试opus解码', + icon: Icons.bluetooth_searching, + iconBgColor: isDarkMode + ? Colors.green[900]!.withOpacity(0.3) + : Colors.green[100]!, + iconColor: + isDarkMode ? Colors.green[300]! : Colors.green[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.opusTest); + }, + isDarkMode: isDarkMode, + ), + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), // BLE测试 // _buildSimpleNavigationSetting( diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 3bf9f9071..4da069bc9 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -21,6 +21,8 @@ class SplashController extends GetxController { final GetStorage _storage = GetStorage(); static const String _tag = 'SplashCtrl'; + final versionService = VersionUpdateService.to; + // 进度条动画控制 final _loadingProgress = 0.0.obs; double get loadingProgress => _loadingProgress.value; @@ -259,7 +261,10 @@ class SplashController extends GetxController { /// 异步获取登录Token Future getLoginToken() async { try { - final response = await Api.tokenlogin(); + final appVersion = versionService.cachedAppVersion; + final response = await Api.tokenlogin({ + 'version': appVersion, + }); if (response?['user'] != null && response['user'] != '') { final user = User.fromJson(response); await _storage.write('user_info', user.toJson()); diff --git a/lib/modules/translation/models/translation_models.dart b/lib/modules/translation/models/translation_models.dart index 76acdad24..4f9679539 100644 --- a/lib/modules/translation/models/translation_models.dart +++ b/lib/modules/translation/models/translation_models.dart @@ -64,13 +64,13 @@ class TranslationItem { String get sourceLanguageName { final languageManager = Get.find(); return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? - '未知语言'; + 'meetingUnknownLanguage'.tr; // 对应中文:未知语言 } - + /// 获取目标语言的中文名称 String get targetLanguageName { final languageManager = Get.find(); return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? - '未知语言'; + 'meetingUnknownLanguage'.tr; // 对应中文:未知语言 } } \ No newline at end of file diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 6fc1400c2..648b37238 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/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 isplaymusic = false; + // 打断防抖相关 private var lastInterruptTime = 0L private val INTERRUPT_DEBOUNCE_MS = 300L @@ -358,6 +361,11 @@ object AgentService : CoroutineScope { setPackage(context.packageName) } context.sendBroadcast(intent) + QQMusicSingleton.getInstance().autopauseMusic { result -> + if (result.isSuccess) { + isplaymusic = true + } + } } TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容 @@ -369,6 +377,10 @@ object AgentService : CoroutineScope { setPackage(context.packageName) } context.sendBroadcast(intent) + if(isplaymusic){ + isplaymusic = false + QQMusicSingleton.getInstance().resumeMusic { } + } } TtsEventType.ERROR -> { diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 32cbf7d71..af51264a9 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/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 onWakeupSignalReceived() { diff --git a/local_plugins/azure_speech/ios/azure_speech/Package.swift b/local_plugins/azure_speech/ios/azure_speech/Package.swift index 17ed7498e..ef7bf57ea 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Package.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Package.swift @@ -9,13 +9,16 @@ let package = Package( ], dependencies: [ // 依赖speech接口模块 - .package(path: "../../../speech/ios/speech") + .package(path: "../../../speech/ios/speech"), + // 添加对ble_service的依赖 + .package(path: "../../../ble_service/ios/ble_service") ], targets: [ .target( name: "azure_speech", dependencies: [ .product(name: "speech", package: "speech"), + .product(name: "ble-service", package: "ble_service"), "MicrosoftCognitiveServicesSpeech" ], path: "Sources", // 修改为包含整个 Sources 目录 diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift index fa5c166eb..fa4f5d684 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift @@ -3,6 +3,7 @@ import AVFoundation import MicrosoftCognitiveServicesSpeech import speech import os.log +import Network // MARK: - 基于微软Azure语音服务的ASR实现 /** @@ -21,6 +22,12 @@ public class AzureAsrHelper: NSObject { private var recognizer: SPXSpeechRecognizer? private var audioConfig: SPXAudioConfiguration? + // 网络监听相关 + private var networkMonitor: NWPathMonitor? + private var networkQueue: DispatchQueue? + private var isNetworkAvailable = true + private var continuousCallback: ContinuousRecognizeCallback? + // 状态管理 private var _isContinuousRecognitionActive = false @@ -110,20 +117,93 @@ public class AzureAsrHelper: NSObject { // 预初始化音频组件 preInitializeAudioComponents() + // 初始化网络监听 + initNetworkMonitoring() + return true } catch { os_log("初始化失败: %{public}@", log: log, type: .error, error.localizedDescription) 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() { if audioStream == nil { - audioStream = SimpleAudioReceiver(parentHelper: self) + audioStream = SimpleAudioReceiver() audioStream?.initAudioRecord() } @@ -149,7 +229,11 @@ public class AzureAsrHelper: NSObject { ) -> Bool { guard speechConfig != nil else { print("语音服务未初始化") - //callback.onError("语音服务未初始化") + return false + } + + // 检查网络状态 + if !checkNetworkStatus() { return false } @@ -170,6 +254,8 @@ public class AzureAsrHelper: NSObject { } return false } + + print("startContinuousRecognition:\(audioSourceType)") // 启动音频处理 audioStream?.startAudioRecord(audioSourceType: audioSourceType == .microphone ? .microphone : .external, audioDataCallback: audioDataCallback) @@ -381,7 +467,7 @@ public class AzureAsrHelper: NSObject { * 设置麦克风音频流 */ private func setupMicrophoneStream() { - audioStream = SimpleAudioReceiver(parentHelper: self) + audioStream = SimpleAudioReceiver() // 【优化】检查音频配置是否已存在 if audioConfig == nil, let pushStream = audioStream?.pushAudioStream { 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}") - // 重设识别器 + // 保存回调引用用于网络事件 + self.continuousCallback = callback + + // 重设识别器 if (!setupRecognizer()) { return false } - guard let recognizer = recognizer else { return false} - // 正在识别事件 + guard let recognizer = recognizer else { return false } + + // 正在识别事件 recognizer.addRecognizingEventHandler { [weak self] (_, event) in guard let self = self else { return } let result = event.result @@ -532,7 +622,12 @@ public class AzureAsrHelper: NSObject { 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 } @@ -575,7 +670,7 @@ public class AzureAsrHelper: NSObject { if audioStream == nil { // 创建外部音频拉流对象 - audioStream = SimpleAudioReceiver(parentHelper: self) + audioStream = SimpleAudioReceiver() audioStream?.initAudioRecord() } @@ -702,6 +797,43 @@ public class AzureAsrHelper: NSObject { */ 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 语言检测 @@ -754,3 +886,4 @@ public class AzureAsrHelper: NSObject { // "originalCode": topResult.language // ] // } + diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift index 4fbb39d9c..0f5c93eab 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift @@ -5,6 +5,8 @@ import MicrosoftCognitiveServicesSpeech // 自定义语音处理组件,提供音频流处理等功能 import speech import os.log +// 导入蓝牙服务模块,用于处理蓝牙设备回调 +import ble_service /** * Azure Speech Plugin @@ -32,6 +34,14 @@ import os.log // 是否已添加TTS事件监听器 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? @@ -39,7 +49,9 @@ import os.log // 插件注册 public static func register(with registrar: FlutterPluginRegistrar) { let instance = AzureSpeechPlugin() - + // 2. 初始化BleService并注册回调 + BleService.shared.addCallback(instance) + // 初始化ASR通道 let asrChannel = FlutterMethodChannel(name: "azure_speech/asr", binaryMessenger: registrar.messenger()) registrar.addMethodCallDelegate(instance, channel: asrChannel) @@ -60,6 +72,17 @@ import os.log 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事件通道 let asrEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) asrEventChannel.setStreamHandler(instance) @@ -69,6 +92,14 @@ import os.log let ttsEventChannel = FlutterEventChannel(name: "azure_speech/tts_events", binaryMessenger: registrar.messenger()) ttsEventChannel.setStreamHandler(instance) instance.ttsEventChannel = ttsEventChannel + + // 初始化TTS事件通道 + let astEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) + astEventChannel.setStreamHandler(instance) + instance.astEventChannel = astEventChannel + + + } // 发送ASR事件方法 internal func sendAsrEvent(_ event: [String: Any]) { @@ -153,20 +184,21 @@ import os.log return } - // 解析音频源类型 - let audioSourceType: AzureAsrHelper.AudioSourceType - if let args = call.arguments as? [String: Any], - let audioSourceString = args["audioSourceType"] as? String { - switch audioSourceString.lowercased() { - case "external": - audioSourceType = .external - default: - audioSourceType = .microphone - } - } else { - audioSourceType = .microphone + // 解析音频源类型参数 + guard let args = call.arguments as? [String: Any], + let isExternalActive = args["audioSourceType"] as? Bool else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) + return } + // 根据参数确定音频源类型 + let audioSourceType = isExternalActive ? + AzureAsrHelper.AudioSourceType.external : + AzureAsrHelper.AudioSourceType.microphone + + print("startContinuousRecognition: \(audioSourceType)") + + // 启动连续识别 let success = azureAsrHelper.startContinuousRecognition( audioSourceType: audioSourceType ) @@ -184,17 +216,17 @@ import os.log callback: currentAsrCallback! ) result(success) - - case "disableBluetoothAudio": - azureAsrHelper.disableBluetoothAudio(); - return - - case "restoreOriginalAudioState": - azureAsrHelper.restoreOriginalAudioState(); - return - + + case "disableBluetoothAudio": + azureAsrHelper.disableBluetoothAudio(); + return + + case "restoreOriginalAudioState": + azureAsrHelper.restoreOriginalAudioState(); + return + case "stopContinuousRecognition": - + let success = azureAsrHelper.stopContinuousRecognition() currentAsrCallback = nil result(success) @@ -287,15 +319,15 @@ import os.log } catch { result(FlutterError(code: "ENABLERECORD_ERROR", message: error.localizedDescription, details: nil)) } - - + + case "pauseRecord": azureAsrHelper.pauseRecord() result(true) - // case "resumeRecord": - // azureAsrHelper.resumeRecord() - // result(true) + // case "resumeRecord": + // azureAsrHelper.resumeRecord() + // result(true) case "stopRecord": guard let args = call.arguments as? [String: Any], @@ -311,16 +343,16 @@ import os.log } case "disableBluetoothAudio": print("disableBluetoothAudio") - azureAsrHelper.disableBluetoothAudio(); - azureTtsHelper.stop() + azureAsrHelper.disableBluetoothAudio(); + azureTtsHelper.stop() result(true) case "restoreOriginalAudioState": print("restoreOriginalAudioState") - azureAsrHelper.restoreOriginalAudioState(); - azureTtsHelper.stop() + azureAsrHelper.restoreOriginalAudioState(); + azureTtsHelper.stop() result(true) case "setAudioConfig": @@ -415,13 +447,13 @@ import os.log if (type == 0) { // 默认(如果有耳机选耳机,否则使用系统扬声器) azureAsrHelper.restoreOriginalAudioState(); - // azureTtsHelper.setAudioOutputDevice() + // azureTtsHelper.setAudioOutputDevice() } else if (type == 1) { // 强制使用声器 azureAsrHelper.disableBluetoothAudio(); - // azureTtsHelper.setAudioOutputDevice() + // azureTtsHelper.setAudioOutputDevice() } else if (type == 2) { // 强制使用耳机 @@ -437,60 +469,388 @@ import os.log 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: - ASR回调包装器 -private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback { +// MARK: - AST Event Callback +/** + * AST事件回调类 + * 实现 IntegratedSpeechTranslationService.ServiceEventCallback 协议 + */ +private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventCallback { private weak var plugin: AzureSpeechPlugin? + /** + * 构造函数 + * @param plugin AzureSpeechPlugin 实例的弱引用 + */ init(plugin: AzureSpeechPlugin) { self.plugin = plugin } - func onResult(_ text: String, _ detectedLanguage: String) { - plugin?.sendAsrEvent([ - "type": "result", - "text": text, - "detectedLanguage": detectedLanguage - ]) + /** + * 服务初始化完成回调 + */ + public func onServiceInitialized() { +// plugin?.sendAsrEvent([ +// "type": "serviceInitialized" +// ]) } - func onRecognizing(_ recognizing: String, _ detectedLanguage: String) { - plugin?.sendAsrEvent([ - "type": "recognizing", - "text": recognizing, - "detectedLanguage": detectedLanguage - ]) + /** + * 识别中回调 + * @param text 正在识别的文本 + * @param language 语言 + * @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", - "reason": reason, - "errorDetails": errorDetails - ]) + /** + * 翻译开始回调 + * @param text 要翻译的文本 + */ + public func onTranslationStarted(text: String) { +// plugin?.sendAsrEvent([ +// "type": "translationStarted", +// "text": text +// ]) } - func onError(_ error: String) { - plugin?.sendAsrEvent([ - "type": "error", - "error": error - ]) + /** + * 翻译失败回调 + * @param text 翻译失败的文本 + * @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: - 事件处理 extension AzureSpeechPlugin: FlutterStreamHandler { public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { @@ -596,6 +956,7 @@ extension AzureSpeechPlugin: AudioDataListener { } } + // 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 + ]) + } +} + diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift index 9c4e3791c..1cf58606a 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift +++ b/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 audioProcessor: SimpleAudioReceiver? + public var audioProcessor: SimpleAudioReceiver? private var audioConfig: SPXAudioConfiguration? // 录音文件处理 @@ -333,7 +333,7 @@ import os.log * 初始化音频处理器 */ private func initializeAudioProcessor() { - audioProcessor = SimpleAudioReceiver(parentHelper: self as! AzureAsrHelper) + audioProcessor = SimpleAudioReceiver() audioProcessor?.initAudioRecord() // 检查音频配置是否已存在 if audioConfig == nil, let pushStream = audioProcessor?.pushAudioStream { @@ -486,16 +486,24 @@ import os.log } } - // 合成进行中事件 + // 合成进行中事件 - 修改为返回音频数据 synthesizer?.addSynthesizingEventHandler { [weak self] (synthesizer, event) in guard let self = self else { return } - os_log("合成进行中事件", log: self.log, type: .debug) - // 计算进度(简化版) - let progress: Float = 0.5 + // 安全解包audioData + guard let audioData = event.result.audioData else { + os_log("合成进行中事件,音频数据为空", log: self.log, type: .debug) + return + } + + os_log("合成进行中事件,音频数据长度: %d bytes", log: self.log, type: .debug, audioData.count) + + 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) DispatchQueue.main.async { + self.serviceState.isSynthesizing = false self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false) } } @@ -654,10 +663,8 @@ import os.log } // 进行语音合成 - if let audioData = synthesizeText(translatedText) { - await MainActor.run { - eventCallback?.onSynthesisAudioGenerated(text: translatedText, audioData: audioData) - } + if synthesizeTextStreaming(translatedText) { + print("语音合成已启动") } } else { 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 { os_log("语音合成器未初始化", log: log, type: .error) - return nil + return false } do { let ssml = buildSSML(text: text) - os_log("开始合成语音: %@", log: log, type: .info, text) - + os_log("开始流式合成语音: %@", log: log, type: .info, text) + serviceState.isSynthesizing = true DispatchQueue.main.async { self.eventCallback?.onSynthesisStarted(text: text) self.eventCallback?.onStateChanged(component: "Synthesis", isActive: true) } + try synthesizer.speakSsml(ssml) + - let result = try synthesizer.speakSsml(ssml) - - 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 - } + return true } catch { serviceState.isSynthesizing = false - let errorMessage = "合成异常: \(error.localizedDescription)" + let errorMessage = "合成启动异常: \(error.localizedDescription)" os_log("%@", log: log, type: .error, errorMessage) DispatchQueue.main.async { @@ -743,10 +730,17 @@ import os.log 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 */ diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift index 029ef7785..f9900b3b2 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift @@ -38,14 +38,8 @@ public class SimpleAudioReceiver: NSObject { private var currentRoute: AudioOutputRoute? //public var onAudioData: ((Data) -> Void)? 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) { // 通过父类引用调用方法 - if audioSourceType != .external || !(parentHelper?.isContinuousRecognitionActive() ?? false) { + if audioSourceType != .external { return } // print("外部data=\(data)") diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/Info.plist b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/Info.plist new file mode 100644 index 000000000..21abfc01c --- /dev/null +++ b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/Info.plist @@ -0,0 +1,27 @@ + + + + + AvailableLibraries + + + BinaryPath + JLAudioUnitKit.framework/JLAudioUnitKit + LibraryIdentifier + ios-arm64 + LibraryPath + JLAudioUnitKit.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitKit.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitKit.h new file mode 100644 index 000000000..b7141b1bd --- /dev/null +++ b/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 + +//! 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 + +#import +#import +#import +#import +#import +#import +#import diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitPlayer.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLAudioUnitPlayer.h new file mode 100644 index 000000000..7da831c67 --- /dev/null +++ b/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 +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, JLAudioUnitPlayerType) { + JLAudioUnitPlayerTypeFile, // 支持 MP3/WAV/AAC 等文件格式(AVAudioPlayer) + JLAudioUnitPlayerTypePCM // 支持 PCM 流式播放(Audio Queue) +}; + +@class JLAudioUnitPlayer; + +@protocol JLAudioPlayerDelegate +@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 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 diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusDecoder.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusDecoder.h new file mode 100644 index 000000000..711e2e99a --- /dev/null +++ b/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 +#import + +NS_ASSUME_NONNULL_BEGIN +@class JLOpusDecoder; + +/// Opus 解码代理 +@protocol JLOpusDecoderDelegate + +/// 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 delegate; + +- (instancetype)init NS_UNAVAILABLE; + +/// 初始化 +/// - Parameters: +/// - format: 解码格式 +/// - delegate: 代理 +- (instancetype)initDecoder:(JLOpusFormat *)format delegate:(id)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 diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusEncoder.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusEncoder.h new file mode 100644 index 000000000..ef8230537 --- /dev/null +++ b/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 +#import + +NS_ASSUME_NONNULL_BEGIN +@class JLOpusEncoder; + +/// Opus 编码代理 +@protocol JLOpusEncoderDelegate + +/// 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 delegate; + +-(instancetype)init NS_UNAVAILABLE; + +/// 初始化 +/// - Parameters: +/// - format: 音频格式 +/// - delegate: 代理 +-(instancetype)initFormat:(JLOpusFormat *)format delegate:(id)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 diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusFormat.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLOpusFormat.h new file mode 100644 index 000000000..4b9b44184 --- /dev/null +++ b/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 + +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 diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWav.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWav.h new file mode 100644 index 000000000..b32ecef2d --- /dev/null +++ b/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 + +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 diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWtg.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLPcmToWtg.h new file mode 100644 index 000000000..231b3e837 --- /dev/null +++ b/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 + +NS_ASSUME_NONNULL_BEGIN + +@interface JLPcm2WtgModel : NSObject + +/// 音频文件 +@property (nonatomic, copy) NSString *pcmPath; + +/// 输出文件 +@property (nonatomic, copy) NSString *wtgPath; + +@end + + +/// JLPcmToWtgProtocol +@protocol JLPcmToWtgDelegate + +/// 音频转码完成 +/// - Parameter model: 音频转码模型 +- (void)convertPcmToWtgDone:(JLPcm2WtgModel *)model; + +@end + +/// 音频转码 +/// PCM 转 WTG +@interface JLPcmToWtg : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/// 音频转码初始化 +/// - Parameter delegate: 音频转码代理 +- (instancetype)initWithDelegate:(id)delegate; + +/// 音频转码 +/// 当前接口模型只限制 PCM 文件格式为:8k 16bit +/// 输入的pcm文件(16bit,小端,8k采样率),wtg_filename:输出的wtg文件 +/// - Parameter model: 音频转码模型 +-(void)convertPcmToWtg:(JLPcm2WtgModel *)model; + +@end + +NS_ASSUME_NONNULL_END diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLSpeexDecoder.h b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Headers/JLSpeexDecoder.h new file mode 100644 index 000000000..ad223d087 --- /dev/null +++ b/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 + +NS_ASSUME_NONNULL_BEGIN +@class JLSpeexDecoder; + +@protocol JLSpeexDelegate + +/// 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)delegate; + +/// 委托 +@property (nonatomic, weak) id 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 diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Info.plist b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Info.plist new file mode 100644 index 000000000..8d397ffb8 Binary files /dev/null and b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Info.plist differ diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/JLAudioUnitKit b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/JLAudioUnitKit new file mode 100755 index 000000000..4dc509fc5 Binary files /dev/null and b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/JLAudioUnitKit differ diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Modules/module.modulemap b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/Modules/module.modulemap new file mode 100644 index 000000000..5e1ae1d3d --- /dev/null +++ b/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 * } +} diff --git a/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/readme.txt b/local_plugins/ble_service/ios/ble_service/JLAudioUnitKit.xcframework/ios-arm64/JLAudioUnitKit.framework/readme.txt new file mode 100644 index 000000000..6789fe6e1 --- /dev/null +++ b/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 + + diff --git a/local_plugins/ble_service/ios/ble_service/Package.swift b/local_plugins/ble_service/ios/ble_service/Package.swift index f3e505b27..e5d2b6c9b 100644 --- a/local_plugins/ble_service/ios/ble_service/Package.swift +++ b/local_plugins/ble_service/ios/ble_service/Package.swift @@ -8,22 +8,29 @@ let package = Package( .library(name: "ble-service", targets: ["ble_service"]) ], dependencies: [ - // YbridOpus SPM 包(替代CocoaPods依赖) - .package(url: "https://github.com/ybrid/opus-swift.git", from: "0.8.0") ], targets: [ .target( name: "ble_service", dependencies: [ - .product(name: "YbridOpus", package: "opus-swift") + "opus", + "JLAudioUnitKit" ], path: "Sources/ble_service", linkerSettings: [ .linkedFramework("AVFoundation"), .linkedFramework("AudioToolbox"), .linkedFramework("CoreBluetooth") - // 移除 AccessorySetupKit 链接,因为它只在 iOS 18+ 可用 ] + ), + // 本地opus框架作为二进制目标(使用xcframework格式) + .binaryTarget( + name: "opus", // 改为与 xcframework 内部模块名称一致 + path: "opus.xcframework" + ), + .binaryTarget( + name: "JLAudioUnitKit", + path: "JLAudioUnitKit.xcframework" ) ] ) \ No newline at end of file diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift index b0c76fa81..f15c40b8b 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift @@ -20,7 +20,12 @@ class BleConst { /** 通知特征UUID - 文档中定义为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 */ static let CLIENT_CHAR_CONFIG_UUID = CBUUID(string: "00002902-0000-1000-8000-00805f9b34fb") diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift index 33f0e9f14..ea066e7d2 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift +++ b/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 onAudioDataReceived1(data: Data) // 唤醒信号回调 func onWakeupSignalReceived() @@ -40,9 +41,14 @@ public class BleService: NSObject { private var centralManager: CBCentralManager? private var peripheralDevice: CBPeripheral? private var writeCharacteristic: CBCharacteristic? + private var callAudioCharacteristic: CBCharacteristic? + private var notifyCharacteristic: CBCharacteristic? private var audioCharacteristic: CBCharacteristic? + // 添加录制文件管理器 + private var recordingFile: RecordingFile? + private var isScanning = false private var scanTimer: Timer? @@ -80,6 +86,24 @@ public class BleService: NSObject { // 是否回复指令码 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 struct Command { @@ -121,16 +145,29 @@ private var cmdReplyType: UInt8 = 0 } // 启动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( - sampleRate: 16000, // 设备端Opus编码采样率 - channels: 1, // 单声道 - frameSize: 640 // 40ms at 16kHz = 640 samples + sampleRate: sampleRate, // 设备端Opus编码采样率 + channels: channels, // 单声道 + frameSize: frameSize // 40ms at 16kHz = 640 samples ) ?? false - + if !opusStarted { 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: - 回调管理 @@ -345,17 +382,18 @@ private var cmdReplyType: UInt8 = 0 peripheralDevice?.delegate = self updateConnectionState(BleConst.STATE_CONNECTED) - if opusProcessor?.isDecoding() != true { + // if opusProcessor?.isDecoding() != true { startOpusDecoding() - } + // } // 清空特征引用,强制重新发现 writeCharacteristic = nil + callAudioCharacteristic = nil notifyCharacteristic = nil audioCharacteristic = nil 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) } @@ -369,6 +407,16 @@ private var cmdReplyType: UInt8 = 0 if centralManager != nil { isManualDisconnect = true // 标记为主动断开 + + // 停止音频发送定时器 + stopAudioSendTimer() + + // 清空音频缓冲区和队列 + audioQueueLock.lock() + audioBuffer.removeAll() + audioDataQueue.removeAll() + audioQueueLock.unlock() + updateConnectionState(BleConst.STATE_DISCONNECTING) centralManager?.cancelPeripheralConnection(peripheral) os_log("🔌 开始断开设备连接", log: logger, type: .info) @@ -481,6 +529,12 @@ private var cmdReplyType: UInt8 = 0 callback.onAudioDataReceived(data: data) } } + /// 通知所有回调接收到音频数据 + private func notifyAudioDataReceived1(data: Data) { + for callback in callbacks { + callback.onAudioDataReceived1(data: data) + } + } /// 通知所有回调接收到唤醒信号 private func notifyWakeupSignalReceived() { @@ -527,11 +581,23 @@ private var cmdReplyType: UInt8 = 0 } // MARK: - 编解码控制 - + + public func writeExternalAudioData(data: Data) { + print("pcm音频进入编码", data.count) + // 确保Opus编码器处于活跃状态(如果不是则启动) + + // 将外部音频数据写入编码流,每次处理1280字节 + opusProcessor?.encodePCMData(data) + + } + /// 打开编码器 func openEncoder() -> Bool { 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 result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) @@ -549,25 +615,50 @@ private var cmdReplyType: UInt8 = 0 os_log("打开解码...", log: logger, type: .info) // 确保Opus解码器处于活跃状态(如果不是则启动) - if opusProcessor?.isDecoding() != true { + //if opusProcessor?.isDecoding() != true { startOpusDecoding() - } + //} // 发送命令通知设备开始解码 let paramData = Data([BleConst.CODEC_CONTROL_DECODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) 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 { - 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]) return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) } - /// 关闭编解码器 + /// 关闭编解码器并停止录制 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解码流,只发送命令通知设备关闭编解码 let paramData = Data([BleConst.CODEC_CONTROL_CLOSE, BleConst.AUDIO_CHANNEL_RIGHT]) @@ -1095,6 +1186,147 @@ extension BleService: SwiftOpusAudioProcessor.AudioDataCallback { func onAudioDataReceived(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 @@ -1392,12 +1624,12 @@ extension BleService: CBCentralManagerDelegate { 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解码器处于活跃状态 - if opusProcessor?.isDecoding() != true { + // if opusProcessor?.isDecoding() != true { startOpusDecoding() - } + // } } public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { @@ -1482,6 +1714,10 @@ extension BleService: CBPeripheralDelegate { os_log("🔍 发现音频服务,开始发现音频特征...", log: logger, type: .debug) // 音频服务,发现音频特征 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) // 先发现描述符,再配置通知 peripheral.discoverDescriptors(for: characteristic) + }else if characteristic.uuid == BleConst.CALL_WRITE_AUDIO_CHAR_UUID { + callAudioCharacteristic = characteristic + os_log("🎵 通话音频特征已设置,开始发现描述符...", log: logger, type: .info) + // 先发现描述符,再配置通知 } } diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/RecordingFile.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/RecordingFile.swift new file mode 100644 index 000000000..6b5824dfc --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift index e44bd6c61..4a664d583 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift +++ b/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() { if let eventSink = statusEventSink { DispatchQueue.main.async { diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift index 1067a9211..5f4c4c69d 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift @@ -1,22 +1,46 @@ import Foundation import AVFoundation -import YbridOpus +import opus +import JLAudioUnitKit @available(iOS 13.0, *) class SwiftOpusAudioProcessor: NSObject { + /// 音频数据回调协议 protocol AudioDataCallback: AnyObject { func onAudioDataReceived(data: Data) + func onAudioDataReceived1(data: Data) + func onEncodedDataReceived(data: Data) // 编码数据回调 } + // MARK: - 私有属性 + + // 状态标志 private var isDecodingActive = false + private var isEncodingActive = false private weak var callback: AudioDataCallback? // Opus 解码器相关 - 使用 YbridOpus C API private var decoder: OpaquePointer? private var sampleRate: Int32 = 16000 private var channels: Int32 = 1 - private let maxFrameSize: Int32 = 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() { super.init() @@ -26,6 +50,10 @@ class SwiftOpusAudioProcessor: NSObject { cleanup() } + + + // MARK: - 解码器管理 + /// 线程安全的解码器初始化 private func initializeOpusDecoder() -> Bool { objc_sync_enter(self) @@ -58,12 +86,21 @@ class SwiftOpusAudioProcessor: NSObject { } } + // MARK: - 公共接口 + + /// 设置音频数据回调 + /// - Parameter callback: 回调对象 func setCallback(_ callback: AudioDataCallback?) { self.callback = callback } + /// 启动Opus解码 + /// - Parameters: + /// - sampleRate: 采样率 + /// - channels: 声道数 + /// - frameSize: 帧大小 + /// - Returns: 是否启动成功 func startDecoding(sampleRate: Int32 = 16000, channels: Int32 = 1, frameSize: Int32 = 40) -> Bool { - // frameSize参数必须与Android版本的packetSize保持一致,确保双平台解码行为一致 if isDecodingActive { return true } @@ -76,10 +113,13 @@ class SwiftOpusAudioProcessor: NSObject { } isDecodingActive = true + print("Opus解码器启动成功 - 采样率: \(sampleRate)Hz, 声道: \(channels)") return true } + /// 停止Opus解码 + /// - Returns: 是否停止成功 func stopDecoding() -> Bool { if !isDecodingActive { return false @@ -87,10 +127,13 @@ class SwiftOpusAudioProcessor: NSObject { isDecodingActive = false destroyOpusDecoder() + print("Opus解码器已停止") return true } + /// 处理音频数据进行解码 + /// - Parameter data: Opus编码的音频数据 func processAudioData(_ data: Data) { guard isDecodingActive, let decoder = decoder, data.count > 0 else { return @@ -102,16 +145,13 @@ class SwiftOpusAudioProcessor: NSObject { return } - // 检查最小数据包大小(Opus最小包通常至少几个字节) guard data.count >= 3 else { print("音频数据包太小: \(data.count) bytes,跳过处理") return } - // 简单的 Opus 头部验证(检查是否是有效的 Opus 数据包格式) + // 简单的 Opus 头部验证 let firstByte = data[0] - // Opus packets start with a TOC (Table of Contents) byte - // 检查TOC字节是否在合理范围内 if firstByte == 0x00 || firstByte == 0xFF { print("可疑的Opus数据包头部: 0x\(String(format: "%02X", firstByte)),跳过处理") return @@ -123,12 +163,11 @@ class SwiftOpusAudioProcessor: NSObject { } /// 改进的 Opus 解码方法,增强容错和线程安全 + /// - Parameter packet: Opus数据包 private func decodeOpusPacket(_ packet: Data) { - // 添加线程安全锁 objc_sync_enter(self) defer { objc_sync_exit(self) } - // 验证解码器状态 guard let decoder = decoder, isDecodingActive else { print("解码器未初始化或已停止") return @@ -138,16 +177,13 @@ class SwiftOpusAudioProcessor: NSObject { var pcmBuffer = [opus_int16](repeating: 0, count: maxSamples * Int(channels)) let decodedSamples = packet.withUnsafeBytes { packetPtr in - // 确保指针有效 guard let uint8Ptr = packetPtr.bindMemory(to: UInt8.self).baseAddress else { print("无法获取数据包指针") return opus_int32(-1) } - // 添加异常捕获机制 var result: opus_int32 = -1 - // 使用 autoreleasepool 确保内存管理 autoreleasepool { result = opus_decode( decoder, @@ -162,12 +198,11 @@ class SwiftOpusAudioProcessor: NSObject { return result } - // 增强的错误处理:检查各种错误情况 + // 增强的错误处理 if decodedSamples <= 0 { let errorCode = decodedSamples - print("Opus解码失败,错误代码: \(errorCode),重新初始化解码器") + print("Opus解码失败,错误代码: \(errorCode)") - // 根据错误类型进行不同的处理 if errorCode == -1 { // OPUS_BAD_ARG print("Opus解码参数错误,执行完全重置") performCompleteReset() @@ -184,18 +219,15 @@ class SwiftOpusAudioProcessor: NSObject { return } - // 验证解码结果的合理性 guard decodedSamples <= maxFrameSize else { print("Opus解码样本数超出预期: \(decodedSamples) > \(maxFrameSize)") resetDecoder() return } - // 提取解码的音频数据 let samplesPerChannel = Int(decodedSamples) let totalSamples = samplesPerChannel * Int(channels) - // 验证总样本数的合理性 guard totalSamples <= pcmBuffer.count else { print("计算的总样本数超出缓冲区: \(totalSamples) > \(pcmBuffer.count)") resetDecoder() @@ -209,31 +241,28 @@ class SwiftOpusAudioProcessor: NSObject { } } - // 重置解码器的简单方法 + /// 重置解码器的简单方法 private func resetDecoder() { print("执行简单解码器重置") destroyOpusDecoder() _ = initializeOpusDecoder() } - // 执行完全重置(包括停止和重启解码) + /// 执行完全重置(包括停止和重启解码) private func performCompleteReset() { print("执行完全解码器重置") let wasActive = isDecodingActive let currentSampleRate = self.sampleRate let currentChannels = self.channels - // 完全停止解码 if wasActive { isDecodingActive = false destroyOpusDecoder() } - // 重新初始化所有状态 self.sampleRate = currentSampleRate self.channels = currentChannels - // 如果之前是活跃状态,重新启动 if wasActive { guard initializeOpusDecoder() else { print("完全重置后重新初始化失败") @@ -243,29 +272,312 @@ class SwiftOpusAudioProcessor: NSObject { print("完全重置成功,解码器已重新启动") } } - + + /// 处理解码后的PCM音频数据 + /// - Parameter pcmData: 解码后的PCM数据数组 private func processPCMData(_ pcmData: [opus_int16]) { - // 转换为 Data let data = Data(bytes: pcmData, count: pcmData.count * MemoryLayout.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.. Bool { return isDecodingActive } - func cleanup() { - stopDecoding() + /// 获取编码器状态 + /// - Returns: 是否正在编码 + func isEncoding() -> Bool { + return isEncodingActive } - // MARK: - 公共接口 - /// 获取当前配置信息 + /// - Returns: 采样率和声道数 func getCurrentConfig() -> (sampleRate: Int32, channels: Int32) { 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.. 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) + } + } } + + + diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/opuse.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/opuse.swift new file mode 100644 index 000000000..04e2fe871 --- /dev/null +++ b/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.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.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) + } + } +} \ No newline at end of file diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/Info.plist b/local_plugins/ble_service/ios/ble_service/opus.xcframework/Info.plist new file mode 100644 index 000000000..67bf2e3b8 --- /dev/null +++ b/local_plugins/ble_service/ios/ble_service/opus.xcframework/Info.plist @@ -0,0 +1,43 @@ + + + + + AvailableLibraries + + + BinaryPath + opus.framework/opus + LibraryIdentifier + ios-x86_64-simulator + LibraryPath + opus.framework + SupportedArchitectures + + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + BinaryPath + opus.framework/opus + LibraryIdentifier + ios-arm64 + LibraryPath + opus.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus.h new file mode 100644 index 000000000..5be73ddf4 --- /dev/null +++ b/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 + *
    + *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • + *
  • frame_size is the duration of the frame in samples (per channel)
  • + *
  • packet is the byte array to which the compressed data is written
  • + *
  • 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.
  • + *
+ * + * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. + * The return value can be negative, 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 OpusEncoder structure. + * @param[in] channels int: 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 opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @param [out] error int*: @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 OpusEncoder*: Encoder state + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: 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 OpusEncoder*: Encoder state + * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusEncoder*: Encoder state + * @param [in] pcm float*: 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 int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusEncoder allocated by opus_encoder_create(). + * @param[in] st OpusEncoder*: 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 OpusEncoder*: 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 OpusDecoder structure. + * @param [in] channels int: 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 opus_int32: Sample rate to decode at (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @param [out] error int*: #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 OpusDecoder*: Decoder state. + * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: 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 OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload* + * @param [out] pcm opus_int16*: 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 must be a multiple of 2.5 ms. + * @param [in] decode_fec int: 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 OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload + * @param [out] pcm float*: 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 must be a multiple of 2.5 ms. + * @param [in] decode_fec int: 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 OpusDecoder*: 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 OpusDecoder allocated by opus_decoder_create(). + * @param[in] st OpusDecoder*: 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 char*: Opus packet to be parsed + * @param [in] len opus_int32: size of data + * @param [out] out_toc char*: TOC pointer + * @param [out] frames char*[48] encapsulated frames + * @param [out] size opus_int16[48] sizes of the encapsulated frames + * @param [out] payload_offset int*: 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 char*: 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 char*: Opus packet. + * This must contain at least one byte of + * data. + * @param [in] Fs opus_int32: 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 char*: 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 char*: Opus packet + * @param [in] len opus_int32: 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 char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @param [in] Fs opus_int32: 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 OpusDecoder*: Decoder state + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: 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 float*: Input PCM and modified PCM + * @param [in] frame_size int Number of samples per channel to process + * @param [in] channels int: Number of channels + * @param [in,out] softclip_mem float*: 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 TARGET_DURATION_MS 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 OpusRepacketizer 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 OpusRepacketizer*: 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 OpusRepacketizer allocated by + * opus_repacketizer_create(). + * @param[in] rp OpusRepacketizer*: 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 OpusRepacketizer*: The repacketizer state to which to + * add the packet. + * @param[in] data const unsigned char*: 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 opus_int32: 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 OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param begin int: The index of the first frame in the current + * repacketizer state to include in the output. + * @param end int: One past the index of the last frame in the + * current repacketizer state to include in the + * output. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1276 for a single frame, + * or for multiple frames, + * 1277*(end-begin). + * However, 1*(end-begin) 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 [begin,end) 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 OpusRepacketizer*: 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 OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1277*opus_repacketizer_get_nb_frames(rp). + * However, + * 1*opus_repacketizer_get_nb_frames(rp) + * 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 const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: 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 const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: 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 const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least 1. + * @param nb_streams opus_int32: 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 const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param nb_streams opus_int32: 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 */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_defines.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_defines.h new file mode 100644 index 000000000..315412dd1 --- /dev/null +++ b/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 /**opus_int32: 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 opus_int32 *: 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 opus_int32: 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 opus_int32 *: 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 opus_int32: Allowed values: + *
+ *
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can + * cause noticeable quality degradation.
+ *
1
VBR (default). The exact type of VBR is controlled by + * #OPUS_SET_VBR_CONSTRAINT.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Hard CBR.
+ *
1
VBR (default). The exact type of VBR may be retrieved via + * #OPUS_GET_VBR_CONSTRAINT.
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default). This creates a maximum of one + * frame of buffering delay assuming a transport with a + * serialization speed of the nominal bitrate.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default).
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @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 opus_int32 *: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @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 opus_int32 *: Allowed values: + *
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @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 opus_int32: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @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 opus_int32 *: 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 opus_int32: Allowed values: + *
+ *
0
Disable inband FEC (default).
+ *
1
Enable inband FEC.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Inband FEC disabled (default).
+ *
1
Inband FEC enabled.
+ *
+ * @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 opus_int32: 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 opus_int32 *: 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 opus_int32: Allowed values: + *
+ *
0
Disable DTX (default).
+ *
1
Enabled DTX.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
DTX disabled (default).
+ *
1
DTX enabled.
+ *
+ * @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 opus_int32: 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 opus_int32 *: 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 really + * know what you are doing. + * @see OPUS_GET_EXPERT_FRAME_DURATION + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_VARIABLE
Optimize the frame size dynamically.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_VARIABLE
Optimize the frame size dynamically.
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
0
Enable prediction (default).
+ *
1
Disable prediction.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Prediction enabled (default).
+ *
1
Prediction disabled.
+ *
+ * @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 opus_uint32 *: 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 opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @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 Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: 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 opus_int32: 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 opus_int32 *: 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 opus_int32 *: 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 opus_int32 *: 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 int: 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 */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_multistream.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_multistream.h new file mode 100644 index 000000000..3622e009f --- /dev/null +++ b/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 opus_int32: The index of the stream whose encoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the encoder. + * @param[out] y OpusEncoder**: 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 opus_int32: The index of the stream whose decoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the decoder. + * @param[out] y OpusDecoder**: 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 + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. + * Normal Opus packets are just a degenerate case of multistream Opus packets, + * and can be encoded or decoded with the multistream API by setting + * streams to 1 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 mapping table defines which decoded channel i + * should be used for each input/output (I/O) channel j. This table is + * typically provided as an unsigned char array. + * Let i = mapping[j] be the index for I/O channel j. + * If i < 2*coupled_streams, then I/O channel j is + * encoded as the left channel of stream (i/2) if i + * is even, or as the right channel of stream (i/2) if + * i is odd. Otherwise, I/O channel j is encoded as + * mono in stream (i - coupled_streams), 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 i must either + * be the special value 255 or be less than streams + coupled_streams. + * + * The output channels specified by the encoder + * should use the + * Vorbis + * channel ordering. 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 int: The total number of streams to encode from the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) 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 opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: 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 int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: 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 OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: 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 int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @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 OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const float*: 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 + * frame_size*channels + * samples. + * @param frame_size int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusMSEncoder allocated by + * opus_multistream_encoder_create(). + * @param st OpusMSEncoder*: 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 OpusMSEncoder*: 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 OpusMSDecoder structure. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) 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 opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @param[out] error int *: 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 OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: 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 OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: 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 + * must be a multiple of 2.5 ms. + * @param decode_fec int: 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 OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: 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 + * must be a multiple of 2.5 ms. + * @param decode_fec int: 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 OpusMSDecoder*: 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 OpusMSDecoder allocated by + * opus_multistream_decoder_create(). + * @param st OpusMSDecoder: Multistream decoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_MULTISTREAM_H */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_types.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Headers/opus_types.h new file mode 100644 index 000000000..b28e03aea --- /dev/null +++ b/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 + + 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 + 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 + 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 + 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 */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Info.plist b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Info.plist new file mode 100644 index 000000000..99c5c68af Binary files /dev/null and b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Info.plist differ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Modules/module.modulemap b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/Modules/module.modulemap new file mode 100644 index 000000000..bb7631ab4 --- /dev/null +++ b/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 * } +} diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/opus b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/opus new file mode 100755 index 000000000..e645bd726 Binary files /dev/null and b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-arm64/opus.framework/opus differ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus.h new file mode 100644 index 000000000..5be73ddf4 --- /dev/null +++ b/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 + *
    + *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • + *
  • frame_size is the duration of the frame in samples (per channel)
  • + *
  • packet is the byte array to which the compressed data is written
  • + *
  • 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.
  • + *
+ * + * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. + * The return value can be negative, 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 OpusEncoder structure. + * @param[in] channels int: 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 opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @param [out] error int*: @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 OpusEncoder*: Encoder state + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: 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 OpusEncoder*: Encoder state + * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusEncoder*: Encoder state + * @param [in] pcm float*: 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 int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusEncoder allocated by opus_encoder_create(). + * @param[in] st OpusEncoder*: 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 OpusEncoder*: 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 OpusDecoder structure. + * @param [in] channels int: 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 opus_int32: Sample rate to decode at (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @param [out] error int*: #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 OpusDecoder*: Decoder state. + * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: 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 OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload* + * @param [out] pcm opus_int16*: 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 must be a multiple of 2.5 ms. + * @param [in] decode_fec int: 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 OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload + * @param [out] pcm float*: 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 must be a multiple of 2.5 ms. + * @param [in] decode_fec int: 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 OpusDecoder*: 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 OpusDecoder allocated by opus_decoder_create(). + * @param[in] st OpusDecoder*: 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 char*: Opus packet to be parsed + * @param [in] len opus_int32: size of data + * @param [out] out_toc char*: TOC pointer + * @param [out] frames char*[48] encapsulated frames + * @param [out] size opus_int16[48] sizes of the encapsulated frames + * @param [out] payload_offset int*: 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 char*: 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 char*: Opus packet. + * This must contain at least one byte of + * data. + * @param [in] Fs opus_int32: 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 char*: 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 char*: Opus packet + * @param [in] len opus_int32: 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 char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @param [in] Fs opus_int32: 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 OpusDecoder*: Decoder state + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: 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 float*: Input PCM and modified PCM + * @param [in] frame_size int Number of samples per channel to process + * @param [in] channels int: Number of channels + * @param [in,out] softclip_mem float*: 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 TARGET_DURATION_MS 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 OpusRepacketizer 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 OpusRepacketizer*: 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 OpusRepacketizer allocated by + * opus_repacketizer_create(). + * @param[in] rp OpusRepacketizer*: 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 OpusRepacketizer*: The repacketizer state to which to + * add the packet. + * @param[in] data const unsigned char*: 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 opus_int32: 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 OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param begin int: The index of the first frame in the current + * repacketizer state to include in the output. + * @param end int: One past the index of the last frame in the + * current repacketizer state to include in the + * output. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1276 for a single frame, + * or for multiple frames, + * 1277*(end-begin). + * However, 1*(end-begin) 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 [begin,end) 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 OpusRepacketizer*: 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 OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1277*opus_repacketizer_get_nb_frames(rp). + * However, + * 1*opus_repacketizer_get_nb_frames(rp) + * 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 const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: 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 const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: 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 const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least 1. + * @param nb_streams opus_int32: 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 const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param nb_streams opus_int32: 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 */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_defines.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_defines.h new file mode 100644 index 000000000..315412dd1 --- /dev/null +++ b/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 /**opus_int32: 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 opus_int32 *: 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 opus_int32: 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 opus_int32 *: 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 opus_int32: Allowed values: + *
+ *
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can + * cause noticeable quality degradation.
+ *
1
VBR (default). The exact type of VBR is controlled by + * #OPUS_SET_VBR_CONSTRAINT.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Hard CBR.
+ *
1
VBR (default). The exact type of VBR may be retrieved via + * #OPUS_GET_VBR_CONSTRAINT.
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default). This creates a maximum of one + * frame of buffering delay assuming a transport with a + * serialization speed of the nominal bitrate.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default).
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @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 opus_int32 *: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @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 opus_int32 *: Allowed values: + *
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @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 opus_int32: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @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 opus_int32 *: 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 opus_int32: Allowed values: + *
+ *
0
Disable inband FEC (default).
+ *
1
Enable inband FEC.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Inband FEC disabled (default).
+ *
1
Inband FEC enabled.
+ *
+ * @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 opus_int32: 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 opus_int32 *: 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 opus_int32: Allowed values: + *
+ *
0
Disable DTX (default).
+ *
1
Enabled DTX.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
DTX disabled (default).
+ *
1
DTX enabled.
+ *
+ * @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 opus_int32: 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 opus_int32 *: 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 really + * know what you are doing. + * @see OPUS_GET_EXPERT_FRAME_DURATION + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_VARIABLE
Optimize the frame size dynamically.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_VARIABLE
Optimize the frame size dynamically.
+ *
+ * @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 opus_int32: Allowed values: + *
+ *
0
Enable prediction (default).
+ *
1
Disable prediction.
+ *
+ * @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 opus_int32 *: Returns one of the following values: + *
+ *
0
Prediction enabled (default).
+ *
1
Prediction disabled.
+ *
+ * @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 opus_uint32 *: 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 opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @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 Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: 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 opus_int32: 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 opus_int32 *: 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 opus_int32 *: 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 opus_int32 *: 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 int: 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 */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_multistream.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_multistream.h new file mode 100644 index 000000000..3622e009f --- /dev/null +++ b/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 opus_int32: The index of the stream whose encoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the encoder. + * @param[out] y OpusEncoder**: 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 opus_int32: The index of the stream whose decoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the decoder. + * @param[out] y OpusDecoder**: 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 + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. + * Normal Opus packets are just a degenerate case of multistream Opus packets, + * and can be encoded or decoded with the multistream API by setting + * streams to 1 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 mapping table defines which decoded channel i + * should be used for each input/output (I/O) channel j. This table is + * typically provided as an unsigned char array. + * Let i = mapping[j] be the index for I/O channel j. + * If i < 2*coupled_streams, then I/O channel j is + * encoded as the left channel of stream (i/2) if i + * is even, or as the right channel of stream (i/2) if + * i is odd. Otherwise, I/O channel j is encoded as + * mono in stream (i - coupled_streams), 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 i must either + * be the special value 255 or be less than streams + coupled_streams. + * + * The output channels specified by the encoder + * should use the + * Vorbis + * channel ordering. 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 int: The total number of streams to encode from the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) 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 opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: 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 int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: 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 OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: 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 int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @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 OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const float*: 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 + * frame_size*channels + * samples. + * @param frame_size int: 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 unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: 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 OpusMSEncoder allocated by + * opus_multistream_encoder_create(). + * @param st OpusMSEncoder*: 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 OpusMSEncoder*: 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 OpusMSDecoder structure. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) 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 opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @param[out] error int *: 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 OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: 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 (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: 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 OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: 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 + * must be a multiple of 2.5 ms. + * @param decode_fec int: 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 OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: 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 + * must be a multiple of 2.5 ms. + * @param decode_fec int: 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 OpusMSDecoder*: 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 OpusMSDecoder allocated by + * opus_multistream_decoder_create(). + * @param st OpusMSDecoder: Multistream decoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_MULTISTREAM_H */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_types.h b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Headers/opus_types.h new file mode 100644 index 000000000..b28e03aea --- /dev/null +++ b/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 + + 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 + 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 + 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 + 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 */ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Info.plist b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Info.plist new file mode 100644 index 000000000..99c5c68af Binary files /dev/null and b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Info.plist differ diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Modules/module.modulemap b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/Modules/module.modulemap new file mode 100644 index 000000000..bb7631ab4 --- /dev/null +++ b/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 * } +} diff --git a/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/opus b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/opus new file mode 100755 index 000000000..a4123a551 Binary files /dev/null and b/local_plugins/ble_service/ios/ble_service/opus.xcframework/ios-x86_64-simulator/opus.framework/opus differ diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt index 6b1235040..08a0578c5 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt +++ b/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 kotlinx.coroutines.delay import com.yunqiinnovation.qq_music.QQMusicSingleton +import kotlin.coroutines.suspendCoroutine +import kotlin.coroutines.resume /** * QQ音乐系统功能处理器 * 负责注册QQ音乐相关的MCP函数 @@ -195,60 +197,110 @@ private class QQMusicLoginHandler(private val context: Context?) : FunctionHandl } } } - private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { override suspend fun handle(arguments: Map): 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 + // 使用 suspendCoroutine 将回调转换为协程 + suspendCoroutine { continuation -> + qqMusicSingleton.searchSongs(keyword) { searchResult -> + val result = if (searchResult.isSuccess) { + val songs = searchResult.getOrNull() ?: emptyList() + + // 根据搜索类型处理结果 + val processedSongs = when (searchType) { + 0 -> { // 歌曲搜索,返回所有结果 + songs + } + else -> { // 专辑、歌手等其他类型,返回所有结果 + songs + } } - } - val songsJson = processedSongs.joinToString(",") { song -> - val songMap = song as Map - // 添加调试日志 - 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\"}" + val songsJson = processedSongs.joinToString(",") { song -> + val songMap = song as Map + // 添加调试日志 + 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}\"}" } - "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}" - } else { - val error = searchResult.exceptionOrNull() - "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" + // 恢复协程执行 + continuation.resume(result) } } - - // 等待回调完成 - delay(5000) - result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" } } catch (e: Exception) { Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" } } } +//private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { +// override suspend fun handle(arguments: Map): 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 +// // 添加调试日志 +// 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 { override suspend fun handle(arguments: Map): String { @@ -310,7 +362,7 @@ private class QQMusicPauseHandler(private val context: Context?) : FunctionHandl qqMusicSingleton.pauseMusic { pauseResult -> result = if (pauseResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"暂停播放\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"暂停播放\"}" } else { val error = pauseResult.exceptionOrNull() "{\"success\": false, \"message\": \"暂停失败: ${error?.message}\"}" @@ -334,7 +386,7 @@ private class QQMusicResumeHandler(private val context: Context?) : FunctionHand qqMusicSingleton.resumeMusic { resumeResult -> result = if (resumeResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"恢复播放\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"恢复播放\"}" } else { val error = resumeResult.exceptionOrNull() "{\"success\": false, \"message\": \"恢复播放失败: ${error?.message}\"}" @@ -358,7 +410,7 @@ private class QQMusicStopHandler(private val context: Context?) : FunctionHandle qqMusicSingleton.stopMusic { stopResult -> result = if (stopResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"停止播放\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"停止播放\"}" } else { val error = stopResult.exceptionOrNull() "{\"success\": false, \"message\": \"停止失败: ${error?.message}\"}" @@ -382,7 +434,7 @@ private class QQMusicNextHandler(private val context: Context?) : FunctionHandle qqMusicSingleton.skipToNext { nextResult -> result = if (nextResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"切换到下一首\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"切换到下一首\"}" } else { val error = nextResult.exceptionOrNull() "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" @@ -406,7 +458,7 @@ private class QQMusicPreviousHandler(private val context: Context?) : FunctionHa qqMusicSingleton.skipToPrevious { previousResult -> result = if (previousResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"切换到上一首\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"切换到上一首\"}" } else { val error = previousResult.exceptionOrNull() "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" @@ -434,7 +486,7 @@ private class QQMusicGetPlaybackStateHandler(private val context: Context?) : Fu val stateJson = state.entries.joinToString(",") { (key, value) -> "\"$key\": \"$value\"" } - "{\"notreply\": true, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}" + "{\"broadcast\": false, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}" } else { val error = stateResult.exceptionOrNull() "{\"success\": false, \"message\": \"获取播放状态失败: ${error?.message}\"}" diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt index 1e4f8a5c1..b7851ff0c 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt +++ b/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, "未播放") + } + } + /** * 暂停音乐 */ diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt index e617a7db6..6cd6de9de 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt +++ b/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 LOGIN_WAIT_DELAY_MS = 30000L // 添加搜索重试相关常量 - private const val SEARCH_TIMEOUT_MS = 50000L - private const val MAX_RETRY_COUNT = 3 + private const val SEARCH_TIMEOUT_MS = 10000L + private const val MAX_RETRY_COUNT = 1 private const val RETRY_DELAY_MS = 1000L @Volatile @@ -194,7 +194,7 @@ class QQMusicSingleton private constructor() { callback(Result.failure(Exception("未安装QQ音乐软件,请先安装QQ音乐软件"))) return } - searchWithRetry(keyword, searchType, 0, callback) + searchWithRetry(keyword, searchType, 1, callback) } /** @@ -413,7 +413,16 @@ class QQMusicSingleton private constructor() { musicManager.pauseMusic(playCallback) }(callback) } - + + + /** + * 暂停音乐 + */ + fun autopauseMusic(callback: (Result) -> Unit) { + executePlaybackAction("自动暂停") { musicManager, playCallback -> + musicManager.autopauseMusic(playCallback) + }(callback) + } /** * 恢复播放 */ diff --git a/pubspec.yaml b/pubspec.yaml index a665292a7..329b96551 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: voitrans description: "Voitrans - AI Voice Assistant." publish_to: "none" -version: 1.0.9+20 +version: 1.0.8+20 environment: sdk: ">=3.3.0 <4.0.0"