Integrate Volcengine realtime voice + Live2D mouth driving

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zyc 2026-05-14 15:39:23 +08:00
parent 72e7df09cd
commit 689fa8936b
134 changed files with 20932 additions and 21 deletions

2
.gitignore vendored
View File

@ -56,6 +56,8 @@ __pycache__/
# ===== Project-specific large/intermediate files =====
# Per-frame PNG dumps generated during GIF recording (intermediate only)
gif-export/clips/*_frames/
# Archive snapshots of clips — the GIFs themselves are already in clips/
gif-export/clips*.zip
# Texture backup created by black_to_transparent.py
**/texture_*.backup.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -43,3 +43,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# Voice chat credentials (never commit)
lib/voice_chat/secrets.dart

Binary file not shown.

View File

@ -0,0 +1,12 @@
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/volcengine/volcengine-specs.git'
platform :ios, '12.0'
inhibit_all_warnings!
target 'SpeechDemo' do
# Add cocoapods dependencyies
pod 'SPEECH_MODULE_NAME', 'SPEECH_MODULE_VERSION'
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:SpeechDemo.xcodeproj">
</FileRef>
</Workspace>

View File

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

View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EC1FD742239A041000A4BE46"
BuildableName = "SpeechDemo.app"
BlueprintName = "SpeechDemo"
ReferencedContainer = "container:SpeechDemo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "[BD-build-infer]"
scriptText = "export BD_ENTRY_SCRIPT=&quot;$0:A&quot;;
if [ -f ~/Library/Caches/com.bytedance.buildinfra/common/pre_build_hook_sync.sh ];then
bash ~/Library/Caches/com.bytedance.buildinfra/common/pre_build_hook_sync.sh 0&lt;/dev/null &gt;&amp;- 2&gt;&amp;- || echo &quot;skip&quot;;
fi;
[ ! -d ~/Library/Caches/com.bytedance.buildinfra/common ] &amp;&amp; mkdir -p ~/Library/Caches/com.bytedance.buildinfra/common;
curl -s -S -f https://ios.bytedance.net/wlapi/tosDownload/iosbinary/indexstore/pre_build_hook_async.sh -o ~/Library/Caches/com.bytedance.buildinfra/common/pre_build_hook_async.sh 0&lt;/dev/null &gt;&amp;- 2&gt;&amp;- &amp;
if [ -f ~/Library/Caches/com.bytedance.buildinfra/common/pre_build_hook_async.sh ];then
bash ~/Library/Caches/com.bytedance.buildinfra/common/pre_build_hook_async.sh 0&lt;/dev/null &gt;&amp;- 2&gt;&amp;- &amp;;
fi;
"
shellToInvoke = "/bin/zsh">
</ActionContent>
</ExecutionAction>
</PreActions>
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "[BD-build-infer]"
scriptText = "export BD_ENTRY_SCRIPT=&quot;$0:A&quot;;
if [ -f ~/Library/Caches/com.bytedance.buildinfra/common/post_build_hook_sync.sh ];then
bash ~/Library/Caches/com.bytedance.buildinfra/common/post_build_hook_sync.sh 0&lt;/dev/null &gt;&amp;- 2&gt;&amp;- || echo &quot;skip&quot;;
fi;
[ ! -d ~/Library/Caches/com.bytedance.buildinfra/common ] &amp;&amp; mkdir -p ~/Library/Caches/com.bytedance.buildinfra/common;
curl -s -S -f https://ios.bytedance.net/wlapi/tosDownload/iosbinary/indexstore/post_build_hook_async.sh -o ~/Library/Caches/com.bytedance.buildinfra/common/post_build_hook_async.sh 0&lt;/dev/null &gt;&amp;- 2&gt;&amp;- &amp;;
if [ -f ~/Library/Caches/com.bytedance.buildinfra/common/post_build_hook_async.sh ];then
bash ~/Library/Caches/com.bytedance.buildinfra/common/post_build_hook_async.sh 0&lt;/dev/null &gt;&amp;- 2&gt;&amp;- &amp;;
fi;
"
shellToInvoke = "/bin/zsh">
</ActionContent>
</ExecutionAction>
</PostActions>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EC1FD75D239A041300A4BE46"
BuildableName = "SpeechDemoTests.xctest"
BlueprintName = "SpeechDemoTests"
ReferencedContainer = "container:SpeechDemo.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EC1FD768239A041300A4BE46"
BuildableName = "SpeechDemoUITests.xctest"
BlueprintName = "SpeechDemoUITests"
ReferencedContainer = "container:SpeechDemo.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(PODS_ROOT)/LLDBInitFile"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EC1FD742239A041000A4BE46"
BuildableName = "SpeechDemo.app"
BlueprintName = "SpeechDemo"
ReferencedContainer = "container:SpeechDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EC1FD742239A041000A4BE46"
BuildableName = "SpeechDemo.app"
BlueprintName = "SpeechDemo"
ReferencedContainer = "container:SpeechDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,17 @@
//
// AfpViewController.h
// SpeechDemo
//
// Created by fangweiwei on 2021/8/31.
// Copyright © 2021 fangweiwei. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AfpViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,340 @@
//
// AfpViewController.m
// SpeechDemo
//
// Created by fangweiwei on 2021/8/31.
// Copyright © 2021 fangweiwei. All rights reserved.
//
#import "AfpViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
@interface AfpViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *fetchResultButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (strong, nonatomic) NSString *debugPath;
// Settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation AfpViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.fetchResultButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.curEngine = nil;
self.engineStarted = FALSE;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_AFP];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
}
#pragma mark - UI Actions
- (IBAction)initEngine:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示语音识别结果"];
}
- (IBAction)startEngine:(id)sender {
NSLog(@"Start engine.");
SEEngineErrorCode ret = [self.curEngine ResetEngine];
NSString *result = [NSString stringWithFormat:@"Reset engine: %d.", ret];
NSLog(@"%@", result);
NSString *path = [self.debugPath stringByAppendingPathComponent:@"test_afp.pcm"];
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error];
if (data) {
// Read success.
SEErrorCode ret = [self.curEngine ProcessAudio:(int16_t *)data.bytes length:(int32_t)(data.length / 2) isFinal:TRUE];
if (ret != SENoError) {
[self setResultText:[NSString stringWithFormat:@"Feed audio data failed: %d, lenght: %lu.", ret, (unsigned long)(data.length / 2)]];
}
}
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
[self setResultText:result];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.fetchResultButton.enabled = TRUE;
}
- (IBAction)stopEngine:(id)sender {
NSLog(@"Stop engine.");
SEEngineErrorCode ret = [self.curEngine ResetEngine];
NSString *result = [NSString stringWithFormat:@"Reset engine: %d.", ret];
NSLog(@"%@", result);
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
[self setResultText:result];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.fetchResultButton.enabled = FALSE;
}
- (IBAction)fetchResult:(id)sender {
SEResultType resultType = [self getResultType];
int resultTypeIdx = [self.settings getOptions:SETTING_AFP_RESULT_TYPE].chooseIdx;
if (resultTypeIdx == 0) {
// Bytes result.
NSData *result;
SEEngineErrorCode ret = [self.curEngine FetchResult:resultType result:&result];
if (ret != SENoError) {
[self setResultText:[NSString stringWithFormat:@"Fetch result failed! Err code: %d.", ret]];
} else {
[self setResultText:@"Fetch result succeed!"];
}
NSLog(@"Fetch result: %d.", ret);
NSString *fileName = [NSString stringWithFormat:@"test_%@_out.bytes", [self getEngineName]];
NSFileHandle *file = [FileUtils openFileForWriting:fileName inPath:self.debugPath];
[FileUtils writeData:result toFileHandel:file];
[FileUtils closeFile:file];
} else {
// Json result.
NSString* result = [self.curEngine FetchStringResult:resultType];
// err_code
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:[result dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];
int errCode = [jsonResult[@"err_code"] intValue];
if (errCode != SENoError) {
[self setResultText:[NSString stringWithFormat:@"Fetch result failed! Err code: %d.", errCode]];
} else {
[self setResultText:@"Fetch result succeed!"];
}
NSLog(@"Fetch result: %d.", errCode);
NSString *fileName = [NSString stringWithFormat:@"test_%@_out.json", [self getEngineName]];
NSFileHandle *file = [FileUtils openFileForWriting:fileName inPath:self.debugPath];
[FileUtils writeString:result toFileHandel:file];
[FileUtils closeFile:file];
}
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
self.engineInitButton.enabled = FALSE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:nil]) {
NSLog(@"Create speech engine failed.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setStringParam:[self getEngineName] forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
}
if (ret == SENoError) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed];
}
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.fetchResultButton.enabled = FALSE;
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.fetchResultButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
- (NSString* )getEngineName {
SettingOptions* engineNameOptions = [self.settings getOptions:SETTING_MUSIC_ENGINE_NAME];
switch (engineNameOptions.chooseIdx) {
case 1:
return SE_COVERSONG_ENGINE;
case 2:
return SE_HUMMING_ENGINE;
case 0:
default:
return SE_AFP_ENGINE;
}
}
- (SEResultType)getResultType {
NSString* engineName = [self getEngineName];
int resultTypeIdx = [self.settings getOptions:SETTING_AFP_RESULT_TYPE].chooseIdx;
// AFP
if ([engineName isEqualToString:SE_AFP_ENGINE]) {
switch (resultTypeIdx) {
case 0:
return SEAfpResult;
case 1:
default:
return SEAfpSliceResult;
}
}
// CoverSong
if ([engineName isEqualToString:SE_COVERSONG_ENGINE]) {
switch (resultTypeIdx) {
case 0:
return SECoversongResult;
case 1:
default:
return SECoverSongSliceResult;
}
}
// Humming
if ([engineName isEqualToString:SE_HUMMING_ENGINE]) {
switch (resultTypeIdx) {
case 0:
return SEHummingResult;
case 1:
default:
return SEHummingSliceResult;
}
}
// Return afp result as default.
return SEAfpResult;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_AFP forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// AsrOfflineViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AsrOfflineViewController : UIViewController
@end

View File

@ -0,0 +1,507 @@
//
// AsrOfflineViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "AsrOfflineViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface AsrOfflineViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long talkingFinisheTimestamp;
@property (nonatomic, assign) long startEngineTimestamp;
@property (strong, nonatomic) NSString *debugPath;
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// settings
@property (strong, nonatomic) Settings *settings;
@end
static NSString *SLARDAR_ASR_SERVICE_NAME = @"asr_statistics";
static NSString *SLARDAR_ASR_EVENT_RESPONSE_DELAY = @"asr_response_delay";
@implementation AsrOfflineViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_ASR_OFFLINE];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPressTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.longPressButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEAsrPartialResult:
[self speechEngineResult:data isFinal:FALSE];
break;
case SEFinalResult:
[self speechEngineResult:data isFinal:TRUE];
break;
case SEVolumeLevel:
NSLog(@"volume level: %s", (char*)data.bytes);
break;
case SEEngineLog:
NSLog(@"engine log: %s", (char*)data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)initEngine:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示语音识别结果"];
}
- (IBAction)startEngine:(id)sender {
NSLog(@"Start engine.");
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
//
[self.curEngine setStringParam:[self.settings getOptionsValue:SETTING_ASR_RESULT_TYPE] forKey:SE_PARAMS_KEY_ASR_RESULT_TYPE_STRING];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
[self.curEngine setIntParam:[self.streamRecorder getChannel] forKey:SE_PARAMS_KEY_CUSTOM_CHANNEL_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"asr_rec_file.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
[self.curEngine sendDirective:SEDirectiveSyncStopEngine];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
- (IBAction)stopEngine:(id)sender {
NSLog(@"Stop engine.");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)longPressTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
NSLog(@"Long press begin.");
[self setResultText:@""];
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
[self.curEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
[self.curEngine setIntParam:[self.streamRecorder getChannel] forKey:SE_PARAMS_KEY_CUSTOM_CHANNEL_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"asr_rec_file.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
[self.curEngine sendDirective:SEDirectiveSyncStopEngine];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
NSLog(@"Long press ended.");
self.talkingFinisheTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
self.engineInitButton.enabled = FALSE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (![self.settings getBool:SETTING_ASR_ENABLE_RESOURCE_DOWNLOAD]) {
[self initEngineInternal];
} else {
SpeechResourceManager *resourceManager = [SpeechResourceManager shareInstance];
if ([resourceManager checkModelExist:[self getAsrModelName]]) {
[resourceManager checkModelVersion:[self getAsrModelName] completion:^(SEResourceStatus status, BOOL needUpdate, NSData *data) {
if (status == kSERSuccess) {
if (needUpdate) {
[self fetchAsrResource];
} else {
[self initEngineInternal];
}
} else {
NSLog(@"Model check failed: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[self.resultTextView setText:@"Failed to check asr resource"];
[self speechEngineInitFailed];
}
}];
} else {
[self fetchAsrResource];
}
}
}
- (void)fetchAsrResource {
SpeechResourceManager *resourceManager = [SpeechResourceManager shareInstance];
[resourceManager fetchModelByName:[self getAsrModelName] completion:^(SEResourceStatus status, NSData* data) {
if (status == kSERSuccess) {
[self initEngineInternal];
} else {
[self.resultTextView setText:@"Failed to fetch asr resource"];
[self speechEngineInitFailed];
}
}];
}
- (void)initEngineInternal {
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
NSString* modelsPath = [NSString stringWithFormat:@"%@/models", self.debugPath];
if ([self.settings getBool:SETTING_ASR_ENABLE_RESOURCE_DOWNLOAD]) {
SpeechResourceManager *resourceManager = [SpeechResourceManager shareInstance];
modelsPath = [resourceManager getModelPath:[self getAsrModelName]];
}
NSLog(@"Asr model path: %@", modelsPath);
//线
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_LICENSE_DIRECTORY_STRING];
NSString* authenticationType = [self getAuthenticationType];
//Authenticate Type
[self.curEngine setStringParam:authenticationType forKey:SE_PARAMS_KEY_AUTHENTICATE_TYPE_STRING];
if ([authenticationType isEqualToString:SE_AUTHENTICATE_TYPE_PRE_BIND]) {
// APP 使线
NSString *licenseName = [self.settings getString:SETTING_LICENSE_NAME];
NSString *licenseBusiId = [self.settings getString:SETTING_LICENSE_BUSI_ID];
// ID, 线使,
//
[self.curEngine setStringParam:licenseName forKey:SE_PARAMS_KEY_LICENSE_NAME_STRING];
// ID,
[self.curEngine setStringParam:licenseBusiId forKey:SE_PARAMS_KEY_LICENSE_BUSI_ID_STRING];
} else if ([authenticationType isEqualToString:SE_AUTHENTICATE_TYPE_LATE_BIND]) {
// APP 使使线
//线Authenticate Address
[self.curEngine setStringParam:SDEF_AUTHENTICATE_ADDRESS forKey:SE_PARAMS_KEY_AUTHENTICATE_ADDRESS_STRING];
//线Authenticate Uri
[self.curEngine setStringParam:SDEF_AUTHENTICATE_URI forKey:SE_PARAMS_KEY_AUTHENTICATE_URI_STRING];
NSString* curBusinessKey = [self.settings getString:SETTING_BUSINESS_KEY];
NSString* curAuthenticateSecret = [self.settings getString:SETTING_AUTHENTICATE_SECRET];
//线Business Key
[self.curEngine setStringParam:curBusinessKey forKey:SE_PARAMS_KEY_BUSINESS_KEY_STRING];
//线Authenticate Secret
[self.curEngine setStringParam:curAuthenticateSecret forKey:SE_PARAMS_KEY_AUTHENTICATE_SECRET_STRING];
}
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setStringParam:@"388808087185088" forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:1 forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
[self.curEngine setStringParam:@"" forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
if ([self.settings getBool:SETTING_ASR_RECORDER_SAVE]) {
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
}
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
[self.curEngine setStringParam:SE_ASR_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
[self.curEngine setIntParam:16000 forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
[self.curEngine setBoolParam:true forKey:SE_PARAMS_KEY_ASR_SHOW_UTTER_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_SHOW_LANGUAGE] forKey:SE_PARAMS_KEY_ASR_SHOW_LANG_BOOL];
[self.curEngine setBoolParam:true forKey:SE_PARAMS_KEY_ASR_SHOW_VOLUME_BOOL];
[self.curEngine setIntParam:SEAsrWorkModeOfflineFlute forKey:SE_PARAMS_KEY_ASR_WORK_MODE_INT];
[self.curEngine setStringParam:modelsPath forKey:SE_PARAMS_KEY_ASR_OFF_RESOURCE_PATH_STRING];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000 || [self.streamRecorder getChannel] != 1) {
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
}
}
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
}
if (ret == SENoError) {
[self speechEngineInitOk];
} else {
[self.resultTextView setText:[NSString stringWithFormat:@"Failed to init engine: %d", ret]];
[self speechEngineInitFailed];
}
}
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
dispatch_async(dispatch_get_main_queue(), ^{
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
});
}
- (void)setHotWords:(NSString*) hotWords {
[self.curEngine sendDirective:SEDirectiveUpdateAsrHotWords data: hotWords];
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
[self.streamRecorder setSpeechEngine:VIEW_ASR_OFFLINE engine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.startEngineTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.longPressButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
dispatch_async(dispatch_get_main_queue(), ^{
long response_delay = 0;
if (isFinal && self.talkingFinisheTimestamp > 0) {
response_delay = [self timeDelayFrom:self.talkingFinisheTimestamp];
self.talkingFinisheTimestamp = 0;
}
NSString* dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
[text appendFormat:@"result: %@", dataStr];
if (isFinal) {
[text appendFormat:@"\nresponse_delay: %ld", response_delay];
}
if (text.length) {
[self.resultTextView setText:[text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
}
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
// [self stopEngine:nil];
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
- (NSString*)getAsrModelName {
return [self.settings getString:SETTING_ASR_MODEL_NAME];
}
#pragma mark - Helper
- (NSString*)getAuthenticationType {
switch ([self.settings getOptions:SETTING_AUTHENTICATION_TYPE].chooseIdx) {
case 0:
return SE_AUTHENTICATE_TYPE_PRE_BIND;
case 1:
return SE_AUTHENTICATE_TYPE_LATE_BIND;
default:
break;
}
return SE_AUTHENTICATE_TYPE_PRE_BIND;
}
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_ASR_OFFLINE forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// AsrViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AsrViewController : UIViewController
@end

View File

@ -0,0 +1,563 @@
//
// AsrViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "AsrViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface AsrViewController () <SpeechEngineDelegate, UITextViewDelegate>
// UI
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *initialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *uninitialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *recordButton;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
// Settings
@property (strong, nonatomic) Settings *settings;
// APP Stream 使
@property (weak, nonatomic) StreamRecorder *streamRecorder;
//
@property (nonatomic, assign) long talkingFinishTimestamp;
@end
@implementation AsrViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.recordButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(recordTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.recordButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_ASR];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams{
//Engine Name
[self.curEngine setStringParam:SE_ASR_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//UID & deviceID: 线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
//
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
if ([self.settings getBool:SETTING_ASR_RECORDER_SAVE]) {
//SDK .wav
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
}
// RECORDER_TYPE_STREAM 16K
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000 || [self.streamRecorder getChannel] != 1) {
// RECORDER_TYPE_STREAM
// SDK
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
// APP
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
[self.curEngine setIntParam:[self.streamRecorder getChannel] forKey:SE_PARAMS_KEY_CUSTOM_CHANNEL_INT];
}
}
NSString *address = [self.settings getString:SETTING_ADDRESS];
if (!address.length) {
address = SDEF_DEFAULT_ADDRESS;
}
NSLog(@"Current address: %@", address);
//
[self.curEngine setStringParam:address forKey:SE_PARAMS_KEY_ASR_ADDRESS_STRING];
NSString *uri = [self.settings getString:SETTING_URI];
if (!uri.length) {
uri = SDEF_ASR_DEFAULT_URI;
}
NSLog(@"Current uri: %@", uri);
//Uri
[self.curEngine setStringParam:uri forKey:SE_PARAMS_KEY_ASR_URI_STRING];
NSString* appID = [self.settings getString:SETTING_APPID];
//Appid
[self.curEngine setStringParam:appID.length <= 0 ? SDEF_APPID : appID forKey:SE_PARAMS_KEY_APP_ID_STRING];
//Token
NSString* token = [self.settings getString:SETTING_TOKEN];
[self.curEngine setStringParam:token forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//KVHeaders
[self.curEngine setStringParam:[self.settings getString:SETTING_REQUEST_HEADERS] forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING];
NSString* cluster = [self.settings getString:SETTING_CLUSTER];
NSLog(@"Current cluster: %@", cluster);
//
[self.curEngine setStringParam:cluster.length <= 0 ? SDEF_ASR_DEFAULT_CLUSTER : cluster forKey:SE_PARAMS_KEY_ASR_CLUSTER_STRING];
//线使
[self.curEngine setIntParam:3000 forKey:SE_PARAMS_KEY_ASR_CONN_TIMEOUT_INT];
[self.curEngine setIntParam:5000 forKey:SE_PARAMS_KEY_ASR_RECV_TIMEOUT_INT];
//线00
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_MAX_RETRY_TIMES] forKey:SE_PARAMS_KEY_ASR_MAX_RETRY_TIMES_INT];
//16000
[self.curEngine setIntParam:[self.settings getInt:SETTING_SAMPLE_RATE] forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
//112
[self.curEngine setIntParam:[self.settings getInt:SETTING_CHANNEL] forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
//112SE_PARAMS_KEY_SAMPLE_RATE_INT
[self.curEngine setIntParam:[self.settings getInt:SETTING_CHANNEL] forKey:SE_PARAMS_KEY_UP_CHANNEL_NUM_INT];
}
-(void)configStartAsrParams{
//(DDC)
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_DDC] forKey:SE_PARAMS_KEY_ASR_ENABLE_DDC_BOOL];
//(ITN)
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_NLU_PUNC] forKey:SE_PARAMS_KEY_ASR_SHOW_NLU_PUNC_BOOL];
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_DISABLE_END_PUNC] forKey:SE_PARAMS_KEY_ASR_DISABLE_END_PUNC_BOOL];
// ASRJSON使JSON
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_REQ_PARAMS] forKey:SE_PARAMS_KEY_ASR_REQ_PARAMS_STRING];
//
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_LANGUAGE] forKey:SE_PARAMS_KEY_ASR_LANGUAGE_STRING];
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_SHOW_LANGUAGE] forKey:SE_PARAMS_KEY_ASR_SHOW_LANG_BOOL];
//
[self.curEngine setStringParam:[self.settings getOptionsValue:SETTING_ASR_RESULT_TYPE] forKey:SE_PARAMS_KEY_ASR_RESULT_TYPE_STRING];
//VAD
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_VAD_START_SILENCE_TIME] forKey:SE_PARAMS_KEY_ASR_VAD_START_SILENCE_TIME_INT];
//VAD
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_VAD_END_SILENCE_TIME] forKey:SE_PARAMS_KEY_ASR_VAD_END_SILENCE_TIME_INT];
// 150000ms.
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
// APP
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME] forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
// ASR
if ([self.settings getString:SETTING_ASR_HOTWORDS].length != 0) {
[self setHotWords:[self.settings getString:SETTING_ASR_HOTWORDS]];
}
//"{\"\":\"\"}""古爱玲""谷爱凌"
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_CORRECTWORDS] forKey:SE_PARAMS_KEY_ASR_CORRECT_WORDS_STRING];
NSString* recorderType = [self getRecorderType];
NSLog(@"录音模式: %@", recorderType);
if ([recorderType isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
}
} else if ([recorderType isEqualToString:SE_RECORDER_TYPE_FILE]) {
// 使
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"asr_rec_file.pcm"];
NSLog(@"输入的音频文件路径: %@", file_path);
// 使
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
}
- (void)setHotWords:(NSString*) hotWords {
// ASR "{\"hotwords\":[{\"word\":\"\",\"scale\":2.0}]}"
// scalefloat[1.0,2.0][1.0,50.0]scale
[self.curEngine sendDirective:SEDirectiveUpdateAsrHotWords data: hotWords];
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径: %@", self.debugPath);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret == SENoError) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed:ret];
}
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
dispatch_async(dispatch_get_main_queue(), ^{
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.recordButton.enabled = FALSE;
});
}
- (void)speechEngineInitSucceeded {
[self.streamRecorder setSpeechEngine:VIEW_ASR engine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self setResultText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.uninitialEngineButton.enabled = TRUE;
self.initialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.recordButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
#pragma mark - UI Actions
- (IBAction)initEngineBtnClicked:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngineBtnClicked:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self setResultText:@"点击或按住说话后,展示语音识别结果"];
}
- (IBAction)startEngineBtnClicked:(id)sender {
self.talkingFinishTimestamp = 0;
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartAsrParams];
//
NSLog(@"开启 ASR 云端自动判停");
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
NSLog(@"启用引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)recordTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
self.talkingFinishTimestamp = 0;
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartAsrParams];
//
NSLog(@"关闭 ASR 云端自动判停");
[self.curEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
// Directive
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
self.talkingFinishTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
// Directive
NSLog(@"Directive: SEDirectiveFinishTalking");
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - Message Callback
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功");
[self speechEngineStarted];
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭");
[self speechEngineStopped];
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEConnectionConnected:
NSLog(@"Callback: 建连成功");
break;
case SEAsrPartialResult:
// Callback: ASR
NSLog(@"Callback: ASR 当前请求的部分结果");
[self speechEngineResult:data isFinal:FALSE];
break;
case SEFinalResult:
// Callback: ASR
NSLog(@"Callback: ASR 当前请求最终结果");
[self speechEngineResult:data isFinal:TRUE];
break;
case SEVolumeLevel:
// Callback:
NSLog(@"Callback: 录音音量,%.3f", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] floatValue]);
break;
default:
break;
}
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.recordButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.recordButton.enabled = TRUE;
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
dispatch_async(dispatch_get_main_queue(), ^{
// ASR
long response_delay = 0;
if (isFinal && self.talkingFinishTimestamp > 0) {
response_delay = [self timeDelayFrom:self.talkingFinishTimestamp];
}
// json ASR
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
if (![jsonResult objectForKey:@"result"]) {
return;
}
// UI ASR
NSString *result = [[[jsonResult objectForKey:@"result"] firstObject] objectForKey:@"text"];
if (result.length == 0) {
return;
}
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
[text appendFormat:@"result: %@", result];
[text appendFormat:@"\nreqid: %@", [jsonResult objectForKey:@"reqid"]];
if (isFinal && response_delay > 0) {
[text appendFormat:@"\nresponse_delay: %ld", response_delay];
}
[self setResultText:[text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
// json
id error_json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
if ([error_json isKindOfClass:[NSDictionary class]]) {
NSDictionary *error_info = error_json;
// UI
if ([error_info objectForKey:@"name"] != nil) {
NSString* error_msg = [[error_json objectForKey:@"err_msg"] stringValue];
NSString* reqid = [[error_json objectForKey:@"reqid"] stringValue];
[self setResultText:[NSString stringWithFormat:@"reqid: %@, error: %@", reqid, error_msg]];
} else {
[self setResultText:[NSString stringWithFormat:@"%@", error_info]];
}
}
});
}
#pragma mark - Helper
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_ASR forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "vad_60_60.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "vad_120_120.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "vad_180_180.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,13 @@
//
// AuViewController.h
// SpeechDemo
//
// Created by bytedance on 2023/5/16.
// Copyright © 2023 chengzihao.ds. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AuViewController : UIViewController
@end

View File

@ -0,0 +1,467 @@
//
// AuViewController.m
// SpeechDemo
//
// Created by bytedance on 2023/5/16.
// Copyright © 2023 chengzihao.ds. All rights reserved.
//
#import "AuViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface AuViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long talkingFinisheTimestamp;
@property (nonatomic, assign) long startEngineTimestamp;
@property (strong, nonatomic) NSString *debugPath;
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// settings
@property (strong, nonatomic) Settings *settings;
@end
static NSString *SLARDAR_AU_SERVICE_NAME = @"au_statistics";
static NSString *SLARDAR_AU_EVENT_RESPONSE_DELAY = @"au_response_delay";
@implementation AuViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_AU];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPressTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.longPressButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
- (void)configInitAuParams {
//Engine Name
[self.curEngine setStringParam:SE_AU_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//UID & deviceID: 线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
//
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
if ([self.settings getBool:SETTING_AU_RECORDER_SAVE]) {
//SDK .wav
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_AU_REC_PATH_STRING];
}
//Appid
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
//Token
[self.curEngine setStringParam:[self.settings getString:SETTING_TOKEN] forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//使AUASR
[self.curEngine setIntParam:[self getAuAbility] forKey:SE_PARAMS_KEY_AU_ABILITY_INT];
//
[self.curEngine setStringParam:[self.settings getString:SETTING_ADDRESS] forKey:SE_PARAMS_KEY_AU_ADDRESS_STRING];
//Uri
[self.curEngine setStringParam:[self.settings getString:SETTING_URI] forKey:SE_PARAMS_KEY_AU_URI_STRING];
//
[self.curEngine setStringParam:[self.settings getString:SETTING_CLUSTER] forKey:SE_PARAMS_KEY_AU_CLUSTER_STRING];
//线使
[self.curEngine setIntParam:3000 forKey:SE_PARAMS_KEY_AU_CONN_TIMEOUT_INT];
[self.curEngine setIntParam:5000 forKey:SE_PARAMS_KEY_AU_RECV_TIMEOUT_INT];
//AU使
[self.curEngine setIntParam:[self.settings getInt:SETTING_AU_PROCESS_TIMEOUT] forKey:SE_PARAMS_KEY_AU_PROCESS_TIMEOUT_INT];
//AU使
[self.curEngine setIntParam:[self.settings getInt:SETTING_AU_AUDIO_PACKET_DURATION] forKey:SE_PARAMS_KEY_AU_AUDIO_PACKET_DURATION_INT];
//AU使
[self.curEngine setIntParam:[self.settings getInt:SETTING_AU_EMPTY_PACKET_INTERVAL] forKey:SE_PARAMS_KEY_AU_EMPTY_PACKET_INTERVAL_INT];
// RECORDER_TYPE_STREAM 16K
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000 || [self.streamRecorder getChannel] != 1) {
// RECORDER_TYPE_STREAM
// SDK
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
// APP
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
[self.curEngine setIntParam:[self.streamRecorder getChannel] forKey:SE_PARAMS_KEY_CUSTOM_CHANNEL_INT];
}
}
}
- (void)configStartAuParams {
// Au
// 150000ms.
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
// 12000ms.
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_MUSIC_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_MUSIC_DURATION_INT];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
// 使
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"au_rec_file.pcm"];
NSLog(@"test file path: %@", file_path);
// 使
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
// Asr
//(DDC)
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_DDC] forKey:SE_PARAMS_KEY_ASR_ENABLE_DDC_BOOL];
//(ITN)
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_NLU_PUNC] forKey:SE_PARAMS_KEY_ASR_SHOW_NLU_PUNC_BOOL];
//
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_LANGUAGE] forKey:SE_PARAMS_KEY_ASR_LANGUAGE_STRING];
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_SHOW_LANGUAGE] forKey:SE_PARAMS_KEY_ASR_SHOW_LANG_BOOL];
//
[self.curEngine setStringParam:[self.settings getOptionsValue:SETTING_ASR_RESULT_TYPE] forKey:SE_PARAMS_KEY_ASR_RESULT_TYPE_STRING];
//VAD
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_VAD_START_SILENCE_TIME] forKey:SE_PARAMS_KEY_ASR_VAD_START_SILENCE_TIME_INT];
//VAD
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_VAD_END_SILENCE_TIME] forKey:SE_PARAMS_KEY_ASR_VAD_END_SILENCE_TIME_INT];
//VADVAD
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_VAD_MODE] forKey:SE_PARAMS_KEY_ASR_VAD_MODE_STRING];
// ASR
if ([self.settings getString:SETTING_ASR_HOTWORDS].length != 0) {
[self setHotWords:[self.settings getString:SETTING_ASR_HOTWORDS]];
}
}
- (void)setHotWords:(NSString*) hotWords {
[self.curEngine sendDirective:SEDirectiveUpdateAsrHotWords data: hotWords];
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEPartialResult:
[self speechEngineResult:data isFinal:FALSE];
break;
case SEFinalResult:
[self speechEngineResult:data isFinal:TRUE];
break;
case SEVolumeLevel:
NSLog(@"volume level: %s", (char*)data.bytes);
break;
case SEEngineLog:
NSLog(@"engine log: %s", (char*)data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)initEngine:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示语音理解结果"];
}
- (IBAction)startEngine:(id)sender {
NSLog(@"配置启动参数");
[self configStartAuParams];
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_AU_AUTO_STOP_BOOL];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
- (IBAction)stopEngine:(id)sender {
NSLog(@"Stop engine.");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)longPressTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
NSLog(@"Long press begin.");
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartAuParams];
[self.curEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_AU_AUTO_STOP_BOOL];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
NSLog(@"Long press ended.");
self.talkingFinisheTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
self.engineInitButton.enabled = FALSE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径: %@", self.debugPath);
NSLog(@"配置初始化参数");
[self configInitAuParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"初始化失败,返回值: %ld", ret);
}
if (ret == SENoError) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed];
}
}
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (int)getAuAbility {
SettingOptions* auAbilityOptions = [self.settings getOptions:SETTING_AU_ABILITY];
switch (auAbilityOptions.chooseIdx) {
case 0:
return SEAuAbilityAsr;
case 1:
return SEAuAbilityMusic;
case 2:
return SEAuAbilityAsr | SEAuAbilityMusic;
default:
return SEAuAbilityAsr;
}
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
[self.streamRecorder setSpeechEngine:VIEW_AU engine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.startEngineTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.longPressButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
dispatch_async(dispatch_get_main_queue(), ^{
long response_delay = 0;
if (isFinal && self.talkingFinisheTimestamp > 0) {
response_delay = [self timeDelayFrom:self.talkingFinisheTimestamp];
self.talkingFinisheTimestamp = 0;
}
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
[text appendFormat:@"result: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
if (isFinal) {
[text appendFormat:@"\nresponse_delay: %ld", response_delay];
}
[self.resultTextView setText:[text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
// [self stopEngine:nil];
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_AU forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,17 @@
//
// BiTTSViewController.h
// SpeechDemo
//
// Created by ByteDance on 2025/7/3.
// Copyright © 2025 fangweiwei. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface BiTTSViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,778 @@
//
// BiTTSViewController.m
// SpeechDemo
//
// Created by ByteDance on 2025/7/3.
// Copyright © 2025 fangweiwei. All rights reserved.
//
#import "BiTTSViewController.h"
#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc.h>
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
static int TTS_MAX_RETRY_COUNT = 3;
@interface BiTTSViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *synthesisButton;
@property (weak, nonatomic) IBOutlet UIButton *pauseResumeButton;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;
// Engine State
@property (assign, nonatomic) BOOL engineInited;
@property (assign, nonatomic) BOOL engineStarted;
@property (assign, nonatomic) BOOL engineErrorOccurred;
@property (assign, nonatomic) BOOL playerPaused;
@property (assign, nonatomic) BOOL refTextChanged;
// Settings
@property (strong, nonatomic) Settings *settings;
// 线
@property (strong, nonatomic) NSString *ttsAppId;
@property (strong, nonatomic) NSString *ttsVoiceOnline;
@property (strong, nonatomic) NSString *ttsVoiceTypeOnline;
// tts
@property (assign, nonatomic) int ttsSynthesisIndex;
@property (strong, nonatomic) NSMutableArray* ttsSynthesisText;
@property (strong, nonatomic) NSMutableDictionary* ttsSynthesisMap;
@property (assign, nonatomic) int ttsRetryCount;
@end
@implementation BiTTSViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_BITTS];
self.engineSwitchButton.enabled = TRUE;
[self decorateTextView:self.referTextView];
[self decorateTextView:self.resultTextView];
[self.referTextView setDelegate:self];
self.referTextView.editable = TRUE;
self.engineInited = FALSE;
self.engineStarted = FALSE;
self.engineErrorOccurred = FALSE;
self.playerPaused = FALSE;
// tts
self.ttsSynthesisIndex = 0;
self.ttsSynthesisText = [[NSMutableArray alloc] init];
self.ttsSynthesisMap = [[NSMutableDictionary alloc]init];
self.ttsRetryCount = TTS_MAX_RETRY_COUNT;
[self.referTextView setText:@"愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者流的话。能做事的做事,能发声的发声。有一分热,发一分光。就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。此后如竟没有炬火:我便是唯一的光。"];
[self.statusTextView setText:@"Waiting for init."];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(audioInterruptionHandler:)
name:AVAudioSessionInterruptionNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Notifications
-(void)appWillTerminate:(NSNotification*)note {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVAudioSessionInterruptionNotification
object:nil];
}
- (void)audioInterruptionHandler:(NSNotification*)notification {
AVAudioSessionInterruptionType interruptionType = (AVAudioSessionInterruptionType)[[notification.userInfo objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
AVAudioSessionInterruptionOptions intertuptionOptions = [[notification.userInfo objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntValue];
NSLog(@"Receive audio interruption notification, type: %lu, options: %lu.", (unsigned long)interruptionType, (unsigned long)intertuptionOptions);
if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
NSLog(@"Audio session interruption began");
@synchronized (self) {
[self pausePlayback];
}
} else if (interruptionType == AVAudioSessionInterruptionTypeEnded) {
@synchronized (self) {
NSLog(@"Audio session interruption ended");
if (intertuptionOptions == AVAudioSessionInterruptionOptionShouldResume) {
AVAudioSession *session = [AVAudioSession sharedInstance];
AVAudioSessionCategoryOptions cur_options = session.categoryOptions;
// AudioQueueStart() will return AVAudioSessionErrorCodeCannotInterruptOthers if options didn't contains AVAudioSessionCategoryOptionMixWithOthers
if (!(cur_options & AVAudioSessionCategoryOptionMixWithOthers)) {
AVAudioSessionCategoryOptions readyOptions = AVAudioSessionCategoryOptionMixWithOthers | cur_options;
[session setCategory:AVAudioSessionCategoryPlayback withOptions:readyOptions error:nil];
}
[self resumePlayback];
cur_options = session.categoryOptions;
// Remove AVAudioSessionCategoryOptionMixWithOthers, or the playback will not be interrupted any more
if (cur_options & AVAudioSessionCategoryOptionMixWithOthers) {
[session setCategory:AVAudioSessionCategoryPlayback withOptions:((~AVAudioSessionCategoryOptionMixWithOthers) & cur_options) error:nil];
}
}
}
}
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams {
//Engine Name
[self.curEngine setStringParam:SE_BITTS_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//User ID线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
// true PARAMS_KEY_TTS_AUDIO_PATH_STRING
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_DUMP]
forKey:SE_PARAMS_KEY_TTS_ENABLE_DUMP_BOOL];
// TTS APP 访 tts_{reqid}.wav, {reqid} id
// PARAMS_KEY_TTS_ENABLE_DUMP_BOOL true
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_TTS_AUDIO_PATH_STRING];
//Header
NSString* headerStr = [self.settings getString:SETTING_REQUEST_HEADERS];
if ([headerStr isEqualToString:@""]) {
headerStr = @"{}";
}
[self.curEngine setStringParam:headerStr forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING];
//使 SDK true
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_PLAYER]
forKey:SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL];
// SDK
// SDK SETtsAudioData
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK] ? SETtsDataCallbackModeAll : SETtsDataCallbackModeNone forKey:SE_PARAMS_KEY_ENABLE_PLAYER_AUDIO_CALLBACK_BOOL];
// ------------------------ 线 -----------------------
NSString* appid = [self.settings getString:SETTING_APPID];
self.ttsAppId = appid.length > 0 ? appid : SDEF_APPID;
//线Appid
[self.curEngine setStringParam:self.ttsAppId forKey:SE_PARAMS_KEY_APP_ID_STRING];
NSString* token = [self.settings getString:SETTING_TOKEN];
NSString* ttsAppToken = token.length > 0 ? token : SDEF_TOKEN;
//线Token
[self.curEngine setStringParam:ttsAppToken forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
NSString *address = [self.settings getString:SETTING_ADDRESS];
NSString *ttsAddress = address.length > 0 ? address : SDEF_DEFAULT_ADDRESS;
//
[self.curEngine setStringParam:ttsAddress forKey:SE_PARAMS_KEY_TTS_ADDRESS_STRING];
NSString *uri = [self.settings getString:SETTING_URI];
NSString *ttsUri = uri.length > 0 ? uri : SDEF_BITTS_DEFAULT_URI;
//Uri
[self.curEngine setStringParam:ttsUri forKey:SE_PARAMS_KEY_TTS_URI_STRING];
NSString *resourceId = [self.settings getString:SETTING_RESOURCE_ID];
//id
[self.curEngine setStringParam:resourceId forKey: SE_PARAMS_KEY_RESOURCE_ID_STRING];
//TTS
[self.curEngine setIntParam:10000 forKey:SE_PARAMS_KEY_TTS_CONN_TIMEOUT_INT];
}
-(void)configStartTtsParams {
//
if(![self prepareBiTTSText]) {
char fake_error_info[] = "{err_code:3006, err_msg:\"Invalid input text.\"}";
[self speechEngineError:[NSData dataWithBytes:fake_error_info length:sizeof(fake_error_info)]];
return;
}
}
- (NSString*)getSynthesisText {
if(self.refTextChanged){ //
self.refTextChanged = FALSE;
__block NSString* refText = nil;
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
refText = self.referTextView.text;
});
} else {
refText = self.referTextView.text; // 线
}
if (refText.length > 0) {
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^;!?。!?;…]+[;!?。!?;…]?" options:0 error:&error];
if (!error) {
NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:refText options:0 range:NSMakeRange(0, refText.length)];
for (NSTextCheckingResult *match in matches) {
NSString *sentence = [refText substringWithRange:match.range];
[self addSentence:sentence];
}
}
}
}
if(self.ttsSynthesisIndex == [self.ttsSynthesisText count]){
NSLog(@"no more tts conttent to synthesis");
[self updateTtsResultText:@"No More Text to Synthesis"];
return nil;
}
NSString* text = self.ttsSynthesisText[self.ttsSynthesisIndex];
NSLog(@"Synthesis: %d, text: %@", self.ttsSynthesisIndex, text);
self.ttsSynthesisIndex = (self.ttsSynthesisIndex + 1);
return text;
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
[self initEngineInternal];
}
- (void)initEngineInternal {
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
self.engineInited = (ret == SENoError);
if (self.engineInited) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed:ret];
}
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
}
#pragma mark - UI Actions
- (IBAction)switchEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self clearResult:nil];
self.startEngineButton.enabled = FALSE;
self.synthesisButton.enabled = FALSE;
self.pauseResumeButton.enabled = FALSE;
if (self.engineInited) {
self.referTextView.editable = TRUE;
[self uninitEngine];
self.engineInited = FALSE;
[self.statusTextView setText:@"Waiting for init."];
self.engineSwitchButton.enabled = TRUE;
[self.engineSwitchButton setTitle:@"Init Engine" forState:UIControlStateNormal];
self.stopEngineButton.enabled = FALSE;
} else {
self.referTextView.editable = TRUE;
[self initEngine];
}
}
- (IBAction)Synthesis:(id)sender {
[self triggerSynthesis];
}
- (IBAction)startEngineBtnClicked:(id)sender {
NSLog(@"Start engine, current status: %d", self.engineStarted);
if (!self.engineStarted) {
[self clearResult:nil];
self.engineErrorOccurred = FALSE;
// DirectiveSYNC_STOP
NSLog(@"关闭引擎(同步)");
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
[self configStartTtsParams];
NSLog(@"启动引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
NSString* startPayload = @"{\"req_params\":{\"speaker\":\"zh_female_roumeinvyou_emo_v2_mars_bigtts\",\"audio_params\":{\"emotion\":\"excited\",\"loudness_rate\":50}}}";
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine data:startPayload];
if (SENoError != ret) {
NSString* message = [NSString stringWithFormat:@"发送启动引擎指令失败: %d", ret];
[self sendStartEngineDirectiveFailed:message];
}
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)pausePlayback {
NSLog(@"暂停播放");
NSLog(@"Directive: SEDirectivePausePlayer");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectivePausePlayer];
if (ret == SENoError) {
self.playerPaused = TRUE;
[self.pauseResumeButton setTitle:@"Resume" forState:UIControlStateNormal];
}
NSLog(@"Pause playback status: %d", ret);
}
- (void)resumePlayback {
NSLog(@"继续播放");
NSLog(@"Directive: SEDirectiveResumePlayer");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveResumePlayer];
if (ret == SENoError) {
self.playerPaused = FALSE;
[self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
}
NSLog(@"Resume playback status: %d", ret);
}
- (IBAction)controlPlayingStatus:(id)sender {
NSLog(@"Pause or resume player, current player status: %hhd", self.playerPaused);
if (self.playerPaused) {
[self resumePlayback];
} else {
[self pausePlayback];
}
}
- (IBAction)clearResult:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:@""];
});
}
#pragma mark - Message Callback
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
NSLog(@"Callback: 引擎启动成功: data: %@", data);
[self speechEngineStarted];
break;
case SEEngineStop:
NSLog(@"Callback: 引擎关闭: data: %@", data);
[self speechEngineStopped];
break;
case SEEngineError:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEEventConnectionStarted:
NSLog(@"Callback: SEEventConnectionStarted");
break;
case SEEventConnectionFailed:
NSLog(@"Callback: SEEventConnectionFailed");
break;
case SEEventConnectionFinished:
NSLog(@"Callback: SEEventConnectionFinished");
break;
case SEEventSessionStarted:
NSLog(@"Callback: SEEventSessionStarted");
break;
case SEEventSessionCanceled:
NSLog(@"Callback: SEEventSessionCanceled");
break;
case SEEventSessionFinished:
NSLog(@"Callback: SEEventSessionFinished");
break;
case SEEventSessionFailed:
NSLog(@"Callback: SEEventSessionFailed");
break;
case SEEventTTSSentenceStart:
[self speechStartSynthesis:data];
NSLog(@"Callback: 合成开始 SentenceStart: %@", data);
break;
case SEEventTTSSentenceEnd:
[self speechFinishSynthesis:data];
NSLog(@"Callback: 合成结束 SentenceEnd: %@", data);
case SEEventTTSResponse:
NSLog(@"Callback: 收到合成音频 TTSResponse: %@", data);
case SEEventTTSEnded:
NSLog(@"Callback: TTSEnd: %@", data);
case SEPlayerAudioData:
// NSLog(@"Callback: 播放的pcm音频: %@", data);
break;
case SEPlayerStartPlayAudio:
NSLog(@"Callback: 开始播放TTS音频");
[self speechStartPlaying:nil];
break;
case SEPlayerFinishPlayAudio:
NSLog(@"Callback: 结束播放TTS音频");
[self speechFinishPlaying:nil];
break;
default:
break;
}
}
- (void)speechEngineInitSucceeded {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineSwitchButton.enabled = TRUE;
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@.", self.deviceID]];
self.referTextView.editable = TRUE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.engineSwitchButton.enabled = TRUE;
});
}
- (void)sendSynthesisDirectiveFailed:(NSString*)tipText {
NSLog(@"%@", tipText);
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:tipText];
[self.curEngine sendDirective:SEDirectiveStopEngine];
});
}
- (void)sendStartEngineDirectiveFailed:(NSString*)tipText {
NSLog(@"%@", tipText);
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:tipText];
self.engineStarted = FALSE;
});
}
- (void)speechEngineStarted {
self.ttsRetryCount = TTS_MAX_RETRY_COUNT;
dispatch_async(dispatch_get_main_queue(), ^{
self.referTextView.editable = TRUE;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.synthesisButton.enabled = TRUE;
self.stopEngineButton.enabled = TRUE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
self.referTextView.editable = TRUE;
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.synthesisButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
[self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
self.pauseResumeButton.enabled = FALSE;
self.playerPaused = FALSE;
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL needStop = NO;
id json_obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
if ([json_obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *error_info = json_obj;
NSInteger code = [[error_info objectForKey:@"err_code"] intValue];
switch (code) {
case SETTSLimitQps:
case SETTSLimitCount:
case SETTSServerBusy:
case SETTSLongText:
case SETTSInvalidText:
case SETTSSynthesisTimeout:
case SETTSSynthesisError:
case SETTSSynthesisWaitingTimeout:
case SETTSErrorUnknown:
NSLog(@"When meeting this kind of error, continue to synthesize.");
[self triggerSynthesis];
break;
case SEConnectTimeout:
case SEReceiveTimeout:
case SENetLibError:
// 3
needStop = ![self retrySynthesis];
if (needStop) {
self.engineErrorOccurred = TRUE;
}
break;
default:
needStop = YES;
self.engineErrorOccurred = TRUE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView
setText:[[NSString alloc]
initWithData:data
encoding:NSUTF8StringEncoding]];
});
break;
}
} else {
needStop = YES;
}
if (needStop) {
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
});
}
- (void)scrollTextViewToBottom {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.resultTextView.text.length > 0) {
NSRange bottomRange = NSMakeRange(self.resultTextView.text.length - 1, 1);
[self.resultTextView scrollRangeToVisible:bottomRange];
[self.resultTextView layoutIfNeeded];
}
});
}
-(void)updateTtsResultText:(NSString*) playingId {
if (self.engineErrorOccurred) {
NSLog(@"When a fatal error occurs, prevent the playback text from being displayed.");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSString *currentText = self.resultTextView.text ?: @"";
NSString *newText = [NSString stringWithFormat:@"%@\n%@", currentText, playingId];
[self.resultTextView setText:newText];
});
[self scrollTextViewToBottom];
}
- (void)speechStartSynthesis:(NSData *)data {
if (self.ttsSynthesisIndex < [self.ttsSynthesisText count]) {
NSString* req_id = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self.ttsSynthesisMap setValue:[NSNumber numberWithInt:self.ttsSynthesisIndex] forKey:req_id];
}
dispatch_async(dispatch_get_main_queue(), ^{
self.synthesisButton.enabled = FALSE;
});
}
- (void)speechFinishSynthesis:(NSData *)data {
if (self.ttsRetryCount < TTS_MAX_RETRY_COUNT) {
self.ttsRetryCount = TTS_MAX_RETRY_COUNT;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.synthesisButton.enabled = TRUE;
});
}
- (void)speechStartPlaying:(NSData *)data {
NSLog(@"TTS start playing");
dispatch_async(dispatch_get_main_queue(), ^{
self.pauseResumeButton.enabled = TRUE;
});
}
- (void)speechFinishPlaying :(NSData *)data {
NSLog(@"TTS finish playing");
dispatch_async(dispatch_get_main_queue(), ^{
[self updateTtsResultText:@"playing finished"];
});
[self triggerSynthesis];
}
- (void)speechTtsAudioData:(NSData *)data {
}
- (BOOL)retrySynthesis {
BOOL ret = FALSE;
if (self.engineStarted && self.ttsRetryCount > 0) {
NSLog(@"Retry synthesis for text: %@", self.ttsSynthesisText[self.ttsSynthesisIndex]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self triggerSynthesis];
});
self.ttsRetryCount -= 1;
ret = TRUE;
}
return ret;
}
-(void)triggerSynthesis {
NSString *text = [self getSynthesisText];
if(text == nil){
return;
}
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventStartSession data:@""];
if (ret != SENoError) {
NSLog(@"Start Session faile: %d", ret);
}
NSString* taskRequestJson = [NSString stringWithFormat: @"{\"req_params\":{\"text\":\"%@\"}}", text];
// SEDirectiveTaskRequest sentence_start task respnse... sentence_end
ret = [self.curEngine sendDirective:SEDirectiveEventTaskRequest data:taskRequestJson];
[self updateTtsResultText:text];
NSLog(@"触发合成");
NSLog(@"Directive: SEDirectiveTaskRequest");
if (ret != SENoError) {
NSLog(@"Synthesis faile: %d", ret);
}
ret = [self.curEngine sendDirective:SEDirectiveEventFinishSession data:@""];
if (ret != SENoError) {
NSLog(@"Finish Session faile: %d", ret);
}
}
-(void)addSentence:(NSString*) text {
NSCharacterSet* blankChar = [NSCharacterSet characterSetWithCharactersInString:@" "];
NSString* tmp = [text stringByTrimmingCharactersInSet:blankChar];
if (tmp.length > 0) {
[self.ttsSynthesisText addObject:tmp];
}
}
-(void)resetTtsContext {
self.ttsSynthesisIndex = 0;
[self.ttsSynthesisText removeAllObjects];
[self.ttsSynthesisMap removeAllObjects];
}
-(BOOL)prepareBiTTSText {
[self resetTtsContext];
__block NSString* text = nil;
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
text = self.referTextView.text;
});
} else {
text = self.referTextView.text; // 线
}
if (text.length <= 0) {
text = @"愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者流的话。能做事的做事,能发声的发声。有一分热,发一分光。就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。此后如竟没有炬火:我便是唯一的光。";
}
if (self.ttsSynthesisText == nil || [self.ttsSynthesisText count] <= 0) {
// 使 MESSAGE_TYPE_TTS_PLAYBACK_PROGRESS
NSArray* temp = [text componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@";!?。!?;…"]];
if (text.length > 0) {
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^;!?。!?;…]+[;!?。!?;…]?" options:0 error:&error];
if (!error) {
NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, text.length)];
for (NSTextCheckingResult *match in matches) {
NSString *sentence = [text substringWithRange:match.range];
[self addSentence:sentence];
}
}
}
}
NSLog(@"Synthesis text item num: %ld.", [self.ttsSynthesisText count]);
return [self.ttsSynthesisText count] > 0;
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
if(textView == _referTextView){
self.refTextChanged = TRUE;
}
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_BITTS forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// BigAsrViewController.h
// SpeechDemo
//
// Created by bytedance on 2024/9/9.
// Copyright © 2024 chengzihao.ds. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface BigAsrViewController : UIViewController
@end

View File

@ -0,0 +1,465 @@
//
// BigAsrViewController.m
// SpeechDemo
//
// Created by bytedance on 2024/9/9.
// Copyright © 2024 chengzihao.ds. All rights reserved.
//
#import "BigAsrViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface BigAsrViewController () <SpeechEngineDelegate, UITextViewDelegate>
// UI
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *initialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *uninitialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *recordButton;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
// Settings
@property (strong, nonatomic) Settings *settings;
// APP Stream 使
@property (weak, nonatomic) StreamRecorder *streamRecorder;
@end
@implementation BigAsrViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.recordButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(recordTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.recordButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_BIGASR];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams{
//Engine Name
[self.curEngine setStringParam:SE_ASR_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//UID & deviceID: 线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
//
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
if ([self.settings getBool:SETTING_ASR_RECORDER_SAVE]) {
//SDK .wav
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
}
// RECORDER_TYPE_STREAM 16K
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000 || [self.streamRecorder getChannel] != 1) {
// RECORDER_TYPE_STREAM
// SDK
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
// APP
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
[self.curEngine setIntParam:[self.streamRecorder getChannel] forKey:SE_PARAMS_KEY_CUSTOM_CHANNEL_INT];
}
}
//
[self.curEngine setStringParam:[self.settings getString:SETTING_ADDRESS] forKey:SE_PARAMS_KEY_ASR_ADDRESS_STRING];
//Uri
[self.curEngine setStringParam:[self.settings getString:SETTING_URI] forKey:SE_PARAMS_KEY_ASR_URI_STRING];
//Appid
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
//Token
[self.curEngine setStringParam:[self.settings getString:SETTING_TOKEN] forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//ResourceId
[self.curEngine setStringParam:[self.settings getString:SETTING_RESOURCE_ID] forKey:SE_PARAMS_KEY_RESOURCE_ID_STRING];
//BigAsrSeed
[self.curEngine setIntParam:SEProtocolTypeSeed forKey:SE_PARAMS_KEY_PROTOCOL_TYPE_INT];
//线使
[self.curEngine setIntParam:3000 forKey:SE_PARAMS_KEY_ASR_CONN_TIMEOUT_INT];
[self.curEngine setIntParam:5000 forKey:SE_PARAMS_KEY_ASR_RECV_TIMEOUT_INT];
//线00
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_MAX_RETRY_TIMES] forKey:SE_PARAMS_KEY_ASR_MAX_RETRY_TIMES_INT];
//16000
[self.curEngine setIntParam:[self.settings getInt:SETTING_SAMPLE_RATE] forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
//112
[self.curEngine setIntParam:[self.settings getInt:SETTING_CHANNEL] forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
//112SE_PARAMS_KEY_SAMPLE_RATE_INT
[self.curEngine setIntParam:[self.settings getInt:SETTING_CHANNEL] forKey:SE_PARAMS_KEY_UP_CHANNEL_NUM_INT];
}
-(void)configStartAsrParams{
//(DDC)
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_DDC] forKey:SE_PARAMS_KEY_ASR_ENABLE_DDC_BOOL];
//(ITN)
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_NLU_PUNC] forKey:SE_PARAMS_KEY_ASR_SHOW_PUNC_BOOL];
// ASRJSON使JSON
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_REQ_PARAMS] forKey:SE_PARAMS_KEY_ASR_REQ_PARAMS_STRING];
// 150000ms.
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
// APP
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME] forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
NSString* recorderType = [self getRecorderType];
NSLog(@"录音模式: %@", recorderType);
if ([recorderType isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
}
} else if ([recorderType isEqualToString:SE_RECORDER_TYPE_FILE]) {
// 使
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"asr_rec_file.pcm"];
NSLog(@"输入的音频文件路径: %@", file_path);
// 使
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径: %@", self.debugPath);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret == SENoError) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed:ret];
}
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
dispatch_async(dispatch_get_main_queue(), ^{
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.recordButton.enabled = FALSE;
});
}
- (void)speechEngineInitSucceeded {
[self.streamRecorder setSpeechEngine:VIEW_ASR engine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self setResultText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.uninitialEngineButton.enabled = TRUE;
self.initialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.recordButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
#pragma mark - UI Actions
- (IBAction)initEngineBtnClicked:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngineBtnClicked:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self setResultText:@"点击或按住说话后,展示语音识别结果"];
}
- (IBAction)startEngineBtnClicked:(id)sender {
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartAsrParams];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
NSLog(@"启用引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)recordTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartAsrParams];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
// Directive
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
// Directive
NSLog(@"Directive: SEDirectiveFinishTalking");
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - Message Callback
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功");
[self speechEngineStarted];
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭");
[self speechEngineStopped];
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEConnectionConnected:
NSLog(@"Callback: 建连成功");
break;
case SEAsrPartialResult:
// Callback: ASR
NSLog(@"Callback: ASR 当前请求的部分结果");
[self speechEngineResult:data isFinal:FALSE];
break;
case SEFinalResult:
// Callback: ASR
NSLog(@"Callback: ASR 当前请求最终结果");
[self speechEngineResult:data isFinal:TRUE];
break;
case SEVolumeLevel:
// Callback:
NSLog(@"Callback: 录音音量,%.3f", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] floatValue]);
break;
default:
break;
}
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.recordButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.recordButton.enabled = TRUE;
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
dispatch_async(dispatch_get_main_queue(), ^{
// ASR
NSString* dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self setResultText:[NSString stringWithFormat:@"result: %@", dataStr]];
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
// Error
NSString* errorStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self setResultText:[NSString stringWithFormat:@"error: %@", errorStr]];
});
}
#pragma mark - Helper
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_BIGASR forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,84 @@
cmake_minimum_required(VERSION 3.1)
project(SpeechDemo)
set(DEVELOPMENT_PROJECT_NAME ${CMAKE_PROJECT_NAME})
set(APP_NAME ${CMAKE_PROJECT_NAME})
set(APP_BUNDLE_IDENTIFIER "com.bytedance.ailab.speech.sdk")
set(DEPLOYMENT_TARGET 10.0)
set(APP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(CMAKE_OSX_SYSROOT "iphoneos")
set(CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos;-iphonesimulator")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
set(CMAKE_SUPPRESS_REGENERATION true)
set(CMAKE_IPHONEOS_SYSROOT "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk")
set(CMAKE_IPHONESIMULATOR_SYSROOT "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk")
set(XCODE_STARTUP_TARGET "SpeechDemo")
macro(ADD_FRAMEWORK fwname appname)
find_library(FRAMEWORK_${fwname}
NAMES ${fwname}
PATHS ${CMAKE_IPHONESIMULATOR_SYSROOT}/System/Library ${CMAKE_IPHONEOS_SYSROOT}/System/Library
PATH_SUFFIXES Frameworks
NO_DEFAULT_PATH)
if( ${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND)
MESSAGE(ERROR ": Framework ${fwname} not found")
else()
TARGET_LINK_LIBRARIES(${appname} ${FRAMEWORK_${fwname}})
MESSAGE(STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}")
endif()
endmacro(ADD_FRAMEWORK)
macro(SET_XCODE_PROPERTY TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION)
set(XCODE_RELVERSION_I "${XCODE_RELVERSION}")
if(XCODE_RELVERSION_I STREQUAL "All")
set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}")
else()
set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}")
endif()
endmacro(SET_XCODE_PROPERTY)
file(GLOB_RECURSE APP_HEADER_FILES "${APP_SOURCE_DIR}/*.h")
file(GLOB_RECURSE APP_SOURCE_FILES "${APP_SOURCE_DIR}/*.m")
set(INTERFACE_BUILDER
${APP_SOURCE_DIR}/SpeechSettingItem.xib
${APP_SOURCE_DIR}/Base.lproj/Main.storyboard
${APP_SOURCE_DIR}/Base.lproj/LaunchScreen.storyboard)
add_executable(
${APP_NAME}
MACOSX_BUNDLE
${INTERFACE_BUILDER}
${APP_SOURCE_DIR}/Assets.xcassets
${APP_HEADER_FILES}
${APP_SOURCE_FILES})
set_target_properties(
${APP_NAME}
PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${APP_SOURCE_DIR}/Info.plist"
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
RESOURCE "${APP_SOURCE_DIR}/Assets.xcassets;${INTERFACE_BUILDER}")
SET_XCODE_PROPERTY(${APP_NAME} PRODUCT_BUNDLE_IDENTIFIER ${APP_BUNDLE_IDENTIFIER} All)
SET_XCODE_PROPERTY(${APP_NAME} ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" All)
ADD_FRAMEWORK(UIKit ${APP_NAME})
ADD_FRAMEWORK(Foundation ${APP_NAME})
ADD_FRAMEWORK(CoreFoundation ${APP_NAME})
ADD_FRAMEWORK(AVFoundation ${APP_NAME})
ADD_FRAMEWORK(AudioToolBox ${APP_NAME})
ADD_FRAMEWORK(Accelerate ${APP_NAME})
ADD_FRAMEWORK(AdSupport ${APP_NAME})
ADD_FRAMEWORK(CFNetwork ${APP_NAME})
ADD_FRAMEWORK(CoreGraphics ${APP_NAME})
ADD_FRAMEWORK(CoreImage ${APP_NAME})
ADD_FRAMEWORK(CoreTelephony ${APP_NAME})
ADD_FRAMEWORK(CoreText ${APP_NAME})
ADD_FRAMEWORK(MobileCoreServices ${APP_NAME})
ADD_FRAMEWORK(QuartzCore ${APP_NAME})
ADD_FRAMEWORK(Security ${APP_NAME})
ADD_FRAMEWORK(SystemConfiguration ${APP_NAME})
ADD_FRAMEWORK(JavaScriptCore ${APP_NAME})

View File

@ -0,0 +1,13 @@
//
// CaptViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface CaptViewController : UIViewController
@end

View File

@ -0,0 +1,528 @@
//
// CaptViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "CaptViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface CaptViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
//
@property (nonatomic, assign) long talkingFinisheTimestamp;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// APP Stream 使
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation CaptViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
[self decorateTextView:self.referTextView];
[self decorateTextView:self.resultTextView];
[self.referTextView setDelegate:self];
self.referTextView.editable = TRUE;
[self.statusTextView setText:@"Waiting for init."];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(recordTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.longPressButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_CAPT];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams{
//Engine Name
[self.curEngine setStringParam:SE_CAPT_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//UID: 线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
//
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
if ([self.settings getBool:SETTING_CAPT_RECORDER_SAVE]) {
//SDK .wav
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_CAPT_REC_PATH_STRING];
}
// RECORDER_TYPE_STREAM 16K
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000) {
// RECORDER_TYPE_STREAM
// SDK
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
// APP
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
}
}
//Appid
NSString* appID = [self.settings getString:SETTING_APPID];
[self.curEngine setStringParam:appID.length <= 0 ? SDEF_APPID : appID forKey:SE_PARAMS_KEY_APP_ID_STRING];
//Token
NSString* token = [self.settings getString:SETTING_TOKEN];
[self.curEngine setStringParam:token.length <= 0 ? SDEF_TOKEN : token forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
NSString *address = [self.settings getString:SETTING_ADDRESS];
if (!address.length) {
address = SDEF_DEFAULT_ADDRESS;
}
NSLog(@"Current address: %@", address);
//
[self.curEngine setStringParam:address forKey:SE_PARAMS_KEY_CAPT_ADDRESS_STRING];
NSString *uri = [self.settings getString:SETTING_URI];
if (!uri.length) {
uri = SDEF_CAPT_DEFAULT_MDD_URI;
}
NSLog(@"Current uri: %@", uri);
//Uri
[self.curEngine setStringParam:uri forKey:SE_PARAMS_KEY_CAPT_URI_STRING];
NSString* cluster = [self.settings getString:SETTING_CLUSTER];
if (!cluster.length) {
cluster = SDEF_CAPT_DEFAULT_CLUSTER;
}
NSLog(@"Current cluster: %@", cluster);
//
[self.curEngine setStringParam:cluster forKey:SE_PARAMS_KEY_CAPT_CLUSTER_STRING];
//线使
[self.curEngine setIntParam:12000 forKey:SE_PARAMS_KEY_CAPT_CONN_TIMEOUT_INT];
[self.curEngine setIntParam:8000 forKey:SE_PARAMS_KEY_CAPT_RECV_TIMEOUT_INT];
}
-(void)configStartParams{
NSString* text = self.referTextView.text;
if (text.length < 1) {
text = @"Write down the reference text here";
}
//
[self.curEngine setStringParam:text forKey:SE_PARAMS_KEY_CAPT_REFER_TEXT_STRING];
//SE_CAPT_CORE_TYPE_CN_SENT_RAW
[self.curEngine setStringParam:[self getCoreType] forKey:SE_PARAMS_KEY_CAPT_CORE_TYPE_STRING];
NSInteger difficultyLevel = [self.settings getInt:SETTING_CAPT_DIFFICULTY_LEVEL];
//2123
[self.curEngine setIntParam:difficultyLevel forKey:SE_PARAMS_KEY_CAPT_DIFFICULTY_INT];
NSString *responseMode = SE_CAPT_RESPONSE_MODE_ONCE;
if ([self.settings getBool:SETTING_CAPT_STREAMING_MODE]) {
responseMode = SE_CAPT_RESPONSE_MODE_STREAMING;
}
//SE_CAPT_RESPONSE_MODE_ONCESE_CAPT_RESPONSE_MODE_STREAMING
[self.curEngine setStringParam:responseMode forKey:SE_PARAMS_KEY_CAPT_RESPONSE_MODE_STRING];
//
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_CAPT_AUTO_STOP_BOOL];
// APP
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME]
forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
// 150000ms.
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
// 使
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"capt_rec_file.pcm"];
NSLog(@"输入的音频文件路径: %@", file_path);
// 使
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
}
#pragma mark - Init Methods
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径: %@", self.debugPath);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret == SENoError) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed];
}
}
- (NSString *)getRecorderType {
switch ([self.settings getOptions:SETTING_RECORD_TYPE].chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (NSString *)getCoreType {
switch ([self.settings getOptions:SETTING_CAPT_CORE_TYPE].chooseIdx) {
case 0:
return SE_CAPT_CORE_TYPE_EN_SENT_SCORE;
case 1:
return SE_CAPT_CORE_TYPE_EN_WORD_SCORE;
case 2:
return SE_CAPT_CORE_TYPE_EN_WORD_PRON;
case 3:
return SE_CAPT_CORE_TYPE_CN_SENT_RAW;
default:
break;
}
return @"";
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
}
#pragma mark - UI Actions
- (IBAction)initEngineBtnClicked:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngineBtnClicked:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.statusTextView setText:@"Waiting for init."];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton = FALSE;
}
- (IBAction)startEngineBtnClicked:(id)sender {
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartParams];
//
NSLog(@"开启 CAPT 云端自动判停");
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_CAPT_AUTO_STOP_BOOL];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
NSLog(@"启用引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)recordTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartParams];
//
NSLog(@"关闭 CAPT 云端自动判停");
[self.curEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_CAPT_AUTO_STOP_BOOL];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
// Directive
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
self.talkingFinisheTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
// Directive
NSLog(@"Directive: SEDirectiveFinishTalking");
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功");
[self speechEngineStarted];
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭");
[self speechEngineStopped];
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEAsrPartialResult:
// Callback: Capt SE_PARAMS_KEY_CAPT_RESPONSE_MODE_STRINGSE_CAPT_RESPONSE_MODE_STREAMING
NSLog(@"Callback: Capt 当前请求的部分结果");
break;
case SEFinalResult:
// Callback: Capt
NSLog(@"Callback: Capt 最终评测结果");
[self speechEngineResult:data];
break;
case SEVolumeLevel:
// Callback:
NSLog(@"Callback: 录音音量");
break;
default:
break;
}
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
self.referTextView.editable = TRUE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitSucceeded {
[self.streamRecorder setSpeechEngine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
self.referTextView.editable = TRUE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.engineInitButton.enabled = TRUE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.referTextView.editable = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.longPressButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self getRecorderType] == SE_RECORDER_TYPE_STREAM) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.referTextView.editable = TRUE;
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
long response_delay = 0;
if (self.talkingFinisheTimestamp > 0) {
response_delay = [self timeDelayFrom:self.talkingFinisheTimestamp];
self.talkingFinisheTimestamp = 0;
}
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
NSDictionary *scores = [jsonResult objectForKey:@"scores"];
NSArray *integrity = [jsonResult objectForKey:@"integrity_details"];
if (scores && scores != (id)[NSNull null] && integrity && integrity != (id)[NSNull null]) {
[text appendFormat:@"response_delay: %ld", response_delay];
[text appendFormat:@"\nreqid: %@", [jsonResult objectForKey:@"reqid"]];
[text appendFormat:@"\nscores: %@", [scores description]];
[text appendFormat:@"\nintegrity_details: %@", [integrity description]];
}
NSString *res_text = [NSString stringWithCString:[text cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding];
if (res_text.length) {
[self.resultTextView setText:[res_text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
}
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
// json
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self stopEngineBtnClicked:nil];
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_CAPT forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,17 @@
//
// DialogDelegateViewController.m
// SpeechDemo
//
// Created by bytedance on 2025/3/27.
// Copyright © 2025 bytedance. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface DialogDelegateViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,549 @@
//
// DialogDelegateViewController.m
// SpeechDemo
//
// Created by bytedance on 2025/3/27.
// Copyright © 2025 bytedance. All rights reserved.
//
#import "DialogDelegateViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
#import "utils/DialogMessage.h"
#pragma mark - DialogDelegateViewController
@interface DialogDelegateViewController () <SpeechEngineDelegate, UITextViewDelegate>
// UI
@property (weak, nonatomic) IBOutlet UIButton *initialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *uninitialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *chatTtsTextButton;
@property (weak, nonatomic) IBOutlet UIButton *useServerTriggerTtsButton;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (strong, nonatomic) NSMutableArray *dialogMessages;
// Debug
@property (nonatomic, strong) NSString *deviceID;
@property (strong, nonatomic) NSString *debugPath;
// Speech Engine
@property (strong, nonatomic) SpeechEngine *speechEngine;
@property (assign, nonatomic) BOOL engineStarted;
// Settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation DialogDelegateViewController
static const int MAX_DIALOG_MESSAGE_COUNT = 20;
- (void)viewDidLoad {
[super viewDidLoad];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.chatTtsTextButton.enabled = FALSE;
self.useServerTriggerTtsButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.engineStarted = FALSE;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_DIALOG_DELEGATE];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateView:(UIView *)view {
view.layer.cornerRadius = 5.0f;
view.layer.borderWidth = .25f;
view.layer.borderColor = [UIColor grayColor].CGColor;
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
- (void)configInitParams {
//Engine Name
[self.speechEngine setStringParam:SE_DIALOG_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.speechEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.speechEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//AuthenticationAppId
[self.speechEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
//AuthenticationAppKey
[self.speechEngine setStringParam:[self.settings getString:SETTING_APPKEY] forKey:SE_PARAMS_KEY_APP_KEY_STRING];
//AuthenticationToken
[self.speechEngine setStringParam:[self.settings getString:SETTING_TOKEN] forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//ResourceId
[self.speechEngine setStringParam:[self.settings getString:SETTING_RESOURCE_ID] forKey:SE_PARAMS_KEY_RESOURCE_ID_STRING];
//User ID线
[self.speechEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
//Dialog Address
[self.speechEngine setStringParam:[self.settings getString:SETTING_ADDRESS] forKey:SE_PARAMS_KEY_DIALOG_ADDRESS_STRING];
//Dialog UriUri
[self.speechEngine setStringParam:[self.settings getString:SETTING_URI] forKey:SE_PARAMS_KEY_DIALOG_URI_STRING];
//AEC
[self.speechEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_AEC_BOOL];
//AECAEC
NSString* aecModelPath = [NSString stringWithFormat:@"%@/aec.model", self.debugPath];
[self.speechEngine setStringParam:aecModelPath forKey:SE_PARAMS_KEY_AEC_MODEL_PATH_STRING];
//使Dialog RECORDER STREAM RECORDERSTREAM
[self.speechEngine setStringParam:SE_RECORDER_TYPE_RECORDER forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
//
[self.speechEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_PLAYER_BOOL];
//
[self.speechEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_RECORDER_AUDIO_CALLBACK_BOOL];
//
[self.speechEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_PLAYER_AUDIO_CALLBACK_BOOL];
//
[self.speechEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_DECODER_AUDIO_CALLBACK_BOOL];
//Header
[self.speechEngine setStringParam:[self.settings getString:SETTING_REQUEST_HEADERS] forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING];
//SDK .wav
if ([self.settings getBool:SETTING_DIALOG_ENABLE_RECORDER_DUMP]) {
[self.speechEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DIALOG_RECORDER_PATH_STRING];
} else {
[self.speechEngine setStringParam:@"" forKey:SE_PARAMS_KEY_DIALOG_RECORDER_PATH_STRING];
}
//SDK .wav
if ([self.settings getBool:SETTING_DIALOG_ENABLE_PLAYER_DUMP]) {
[self.speechEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DIALOG_PLAYER_PATH_STRING];
} else {
[self.speechEngine setStringParam:@"" forKey:SE_PARAMS_KEY_DIALOG_PLAYER_PATH_STRING];
}
// TTS
[self.speechEngine setIntParam:SEDialogWorkModeDelegateChatTtsText forKey:SE_PARAMS_KEY_DIALOG_WORK_MODE_INT];
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.speechEngine == nil) {
self.speechEngine = [[SpeechEngine alloc] init];
if (![self.speechEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
}
NSLog(@"Engine version: %@", [self.speechEngine getVersion]);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.speechEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
[self speechEngineInitFailed];
return;
}
[self speechEngineInitOk];
}
- (void)uninitEngine {
if (self.speechEngine) {
NSLog(@"引擎析构");
[self.speechEngine destroyEngine];
self.speechEngine = nil;
NSLog(@"引擎析构完成");
}
[self.statusTextView setText:@"Engine uninited!"];
}
- (void)sayHello {
// Directivesay_hello
NSString* sayHelloJson = [NSString stringWithFormat: @"{\"content\": \"%@\"}", self.referTextView.text];
SEEngineErrorCode ret = [self.speechEngine sendDirective:SEDirectiveEventSayHello data:sayHelloJson];
if (ret != SENoError) {
[self.statusTextView setText:[NSString stringWithFormat: @"开场白触发失败: %d", ret]];
} else {
// DirectiveUseClientTriggerTtsTTS
ret = [self.speechEngine sendDirective:SEDirectiveDialogUseClientTriggerTts];
if (ret != SENoError) {
[self.statusTextView setText:[NSString stringWithFormat: @"播放客户端指定的TTS回复失败: %d", ret]];
} else {
[self showHelloMessage:self.referTextView.text];
}
}
}
#pragma mark - UI Actions
- (IBAction)initEngineBtnClicked:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngineBtnClicked:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.chatTtsTextButton.enabled = FALSE;
self.useServerTriggerTtsButton.enabled = FALSE;
}
- (IBAction)startEngineBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
// Directive
NSLog(@"Directive: SEDirectiveStartEngine");
NSString* startJson = [NSString stringWithFormat: @"{\"dialog\":{\"bot_name\":\"%@\"}}", [self.settings getString:SETTING_DIALOG_BOT_NAME]];
SEEngineErrorCode ret = [self.speechEngine sendDirective:SEDirectiveStartEngine data:startJson];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
} else if (ret == SENoError) {
self.dialogMessages = [[NSMutableArray alloc] init];
self.resultTextView.text = @"";
//
if (self.referTextView.text.length != 0) {
[self sayHello];
}
} else {
[self.statusTextView setText:[NSString stringWithFormat: @"Fail to start engine: %d", ret]];
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
// Directive
NSLog(@"Directive: SEDirectiveStopEngine");
[self.speechEngine sendDirective:SEDirectiveStopEngine];
}
- (IBAction)chatTtsTextBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
NSString* chatTtsText = self.referTextView.text;
// DirectiveChatTtsTextstart:trueend:false
NSString* chatTtsTextJson = [NSString stringWithFormat: @"{\"start\": true, \"content\": \"%@\", \"end\": false}", chatTtsText];
SEEngineErrorCode ret = [self.speechEngine sendDirective:SEDirectiveEventChatTtsText data:chatTtsTextJson];
if (ret != SENoError) {
[self.statusTextView setText:[NSString stringWithFormat: @"自定义TTS回复失败: %d", ret]];
return;
}
// DirectiveChatTtsTextstart:falseend:true
chatTtsTextJson = @"{\"start\": false, \"content\": \"\", \"end\": true}";
ret = [self.speechEngine sendDirective:SEDirectiveEventChatTtsText data:chatTtsTextJson];
if (ret != SENoError) {
[self.statusTextView setText:[NSString stringWithFormat: @"自定义TTS回复失败: %d", ret]];
return;
}
// DirectiveUseClientTriggerTtsTTS
ret = [self.speechEngine sendDirective:SEDirectiveDialogUseClientTriggerTts];
if (ret != SENoError) {
[self.statusTextView setText:[NSString stringWithFormat: @"播放客户端指定的TTS回复失败: %d", ret]];
return;
}
[self updateChatTtsTextMessage:chatTtsText];
}
- (IBAction)useServerTriggerTtsBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
// DirectiveUseServerTriggerTtsTTS
int ret = [self.speechEngine sendDirective:SEDirectiveDialogUseServerTriggerTts];
if (ret != SENoError) {
[self.statusTextView setText:[NSString stringWithFormat: @"播放服务端自动生成的TTS回复失败: %d", ret]];
return;
}
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功: %d", type);
[self speechEngineStarted:strData];
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭: %d", type);
[self speechEngineStopped:strData];
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %d, data: %@", type, strData);
[self showLogMessage:strData];
break;
case SEDialogASRResponse:
[self showUserMessage: data];
break;
case SEDialogASREnded:
[self confirmUserMessage];
break;
case SEDialogChatResponse:
[self showAssistantMessage: data];
break;
case SEDialogASRInfo:
case SEDialogChatEnded:
[self confirmAssistantMessage];
break;
default:
break;
}
}
#pragma mark - SpeechEngine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.initialEngineButton.enabled = FALSE;
self.uninitialEngineButton.enabled = TRUE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
- (void)speechEngineStarted:(NSString *)sessionId {
[self showLogMessage: [NSString stringWithFormat:@"Engine start: %@", sessionId]];
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.chatTtsTextButton.enabled = TRUE;
self.useServerTriggerTtsButton.enabled = TRUE;
[self.statusTextView setText:@"Engine Started!"];
});
}
- (void)speechEngineStopped:(NSString *)sessionId {
[self showLogMessage: [NSString stringWithFormat:@"Engine stop: %@", sessionId]];
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = FALSE;
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.chatTtsTextButton.enabled = FALSE;
self.useServerTriggerTtsButton.enabled = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
});
}
# pragma mark - Helper
- (void)showLogMessage:(NSString *)text {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [[DialogMessage alloc] init];
message.role = ROLE_LOG;
message.text = text;
message.confirmed = true;
[self.dialogMessages addObject:message];
[self updateMessageUI];
});
}
- (void)showHelloMessage:(NSString *)helloMessage {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [[DialogMessage alloc] init];
message.role = ROLE_ASSISTANT;
message.text = helloMessage;
message.confirmed = true;
[self.dialogMessages addObject:message];
[self updateMessageUI];
});
}
- (void)showUserMessage:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_USER];
if (message == nil) {
message = [[DialogMessage alloc] init];
message.role = ROLE_USER;
message.confirmed = false;
[self.dialogMessages addObject:message];
}
// json
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
message.text = [[[jsonResult objectForKey:@"results"] firstObject] objectForKey:@"text"];
[self updateMessageUI];
});
}
- (void)confirmUserMessage {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_USER];
if (message) {
message.confirmed = true;
}
});
}
- (void)showAssistantMessage:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_ASSISTANT];
if (message == nil) {
message = [[DialogMessage alloc] init];
message.role = ROLE_ASSISTANT;
message.text = @"";
message.confirmed = false;
[self.dialogMessages addObject:message];
}
// json
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
message.text = [message.text stringByAppendingString:[jsonResult objectForKey:@"content"]];
[self updateMessageUI];
});
}
- (void)confirmAssistantMessage {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_ASSISTANT];
if (message) {
message.confirmed = true;
}
});
}
- (void)updateChatTtsTextMessage:(NSString *)text {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self.dialogMessages lastObject];
if (message == nil || message.role != ROLE_ASSISTANT) {
message = [[DialogMessage alloc] init];
message.role = ROLE_ASSISTANT;
[self.dialogMessages addObject:message];
}
message.text = text;
message.confirmed = true;
[self updateMessageUI];
});
}
- (DialogMessage*)lastUnconfirmedMessage:(Role)role {
for (DialogMessage* message in [self.dialogMessages reverseObjectEnumerator]) {
if (message.role == role) {
if (!message.confirmed) {
return message;
}
break;
}
}
return nil;
}
- (void)updateMessageUI {
if (self.dialogMessages.count > MAX_DIALOG_MESSAGE_COUNT) {
[self.dialogMessages removeObjectAtIndex:0];
}
NSString* results = @"";
for (DialogMessage* message in self.dialogMessages) {
NSString* role = @"";
switch (message.role) {
case ROLE_USER:
role = @"[USER]:";
break;
case ROLE_ASSISTANT:
role = @"[ASSISTANT]:";
break;
case ROLE_LOG:
role = @"[LOG]:";
break;
}
results = [results stringByAppendingFormat:@"%@%@\n", role, message.text];
}
[self.resultTextView setText:results];
NSRange bottom = NSMakeRange(self.resultTextView.text.length -1, 1);
[self.resultTextView scrollRangeToVisible:bottom];
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_DIALOG_DELEGATE forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,17 @@
//
// DialogViewController.m
// SpeechDemo
//
// Created by bytedance on 2025/3/27.
// Copyright © 2025 bytedance. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface DialogViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,512 @@
//
// DialogViewController.m
// SpeechDemo
//
// Created by bytedance on 2025/3/27.
// Copyright © 2025 bytedance. All rights reserved.
//
#import "DialogViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
#import "utils/DialogMessage.h"
#pragma mark - DialogViewController
@interface DialogViewController () <SpeechEngineDelegate, UITextViewDelegate>
// UI
@property (weak, nonatomic) IBOutlet UIButton *initialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *uninitialEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *chatTextQueryButton;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextView *helloTextView;
@property (weak, nonatomic) IBOutlet UITextView *queryTextView;
@property (strong, nonatomic) NSMutableArray *dialogMessages;
// Debug
@property (nonatomic, strong) NSString *deviceID;
@property (strong, nonatomic) NSString *debugPath;
// Speech Engine
@property (strong, nonatomic) SpeechEngine *speechEngine;
@property (assign, nonatomic) BOOL engineStarted;
// Settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation DialogViewController
NSString *const AEC_MODEL_NAME = @"aec.model";
static const int MAX_DIALOG_MESSAGE_COUNT = 20;
- (void)viewDidLoad {
[super viewDidLoad];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.chatTextQueryButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.engineStarted = FALSE;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_DIALOG];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateView:(UIView *)view {
view.layer.cornerRadius = 5.0f;
view.layer.borderWidth = .25f;
view.layer.borderColor = [UIColor grayColor].CGColor;
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
- (void)configInitParams {
//Engine Name
[self.speechEngine setStringParam:SE_DIALOG_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.speechEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.speechEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//AuthenticationAppId
[self.speechEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
//AuthenticationAppKey
[self.speechEngine setStringParam:[self.settings getString:SETTING_APPKEY] forKey:SE_PARAMS_KEY_APP_KEY_STRING];
//AuthenticationToken
[self.speechEngine setStringParam:[self.settings getString:SETTING_TOKEN] forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//ResourceId
[self.speechEngine setStringParam:[self.settings getString:SETTING_RESOURCE_ID] forKey:SE_PARAMS_KEY_RESOURCE_ID_STRING];
//User ID线
[self.speechEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
//Dialog Address
[self.speechEngine setStringParam:[self.settings getString:SETTING_ADDRESS] forKey:SE_PARAMS_KEY_DIALOG_ADDRESS_STRING];
//Dialog UriUri
[self.speechEngine setStringParam:[self.settings getString:SETTING_URI] forKey:SE_PARAMS_KEY_DIALOG_URI_STRING];
//AEC
[self.speechEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_AEC_BOOL];
//AECAEC
[self.speechEngine setStringParam:[ViewController extractBundleToFilePath:AEC_MODEL_NAME] forKey:SE_PARAMS_KEY_AEC_MODEL_PATH_STRING];
//使Dialog RECORDER STREAM RECORDERSTREAM
[self.speechEngine setStringParam:SE_RECORDER_TYPE_RECORDER forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
//
[self.speechEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_PLAYER_BOOL];
//
[self.speechEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_RECORDER_AUDIO_CALLBACK_BOOL];
//
[self.speechEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_PLAYER_AUDIO_CALLBACK_BOOL];
//
[self.speechEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_DIALOG_ENABLE_DECODER_AUDIO_CALLBACK_BOOL];
//Header
[self.speechEngine setStringParam:[self.settings getString:SETTING_REQUEST_HEADERS] forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING];
//SDK .wav
if ([self.settings getBool:SETTING_DIALOG_ENABLE_RECORDER_DUMP]) {
[self.speechEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DIALOG_RECORDER_PATH_STRING];
} else {
[self.speechEngine setStringParam:@"" forKey:SE_PARAMS_KEY_DIALOG_RECORDER_PATH_STRING];
}
//SDK .wav
if ([self.settings getBool:SETTING_DIALOG_ENABLE_PLAYER_DUMP]) {
[self.speechEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DIALOG_PLAYER_PATH_STRING];
} else {
[self.speechEngine setStringParam:@"" forKey:SE_PARAMS_KEY_DIALOG_PLAYER_PATH_STRING];
}
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.speechEngine == nil) {
self.speechEngine = [[SpeechEngine alloc] init];
if (![self.speechEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
}
NSLog(@"Engine version: %@", [self.speechEngine getVersion]);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.speechEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
[self speechEngineInitFailed];
return;
}
[self speechEngineInitOk];
}
- (void)uninitEngine {
if (self.speechEngine) {
NSLog(@"引擎析构");
[self.speechEngine destroyEngine];
self.speechEngine = nil;
NSLog(@"引擎析构完成");
}
[self.statusTextView setText:@"Engine uninited!"];
}
- (void)sayHello {
NSString* sayHelloJson = [NSString stringWithFormat: @"{\"content\": \"%@\"}", self.helloTextView.text];
SEEngineErrorCode ret = [self.speechEngine sendDirective:SEDirectiveEventSayHello data:sayHelloJson];
if (ret != SENoError) {
NSLog(@"Send directive say hello failed: %d", ret);
} else {
[self showHelloMessage:self.helloTextView.text];
}
}
- (void)chatTextQuery {
NSString* chatTextQueryJson = [NSString stringWithFormat: @"{\"content\": \"%@\"}", self.queryTextView.text];
SEEngineErrorCode ret = [self.speechEngine sendDirective:SEDirectiveEventChatTextQuery data:chatTextQueryJson];
if (ret != SENoError) {
NSLog(@"Send directive chat text query failed: %d", ret);
} else {
[self showChatTextQueryMessage:self.queryTextView.text];
}
}
#pragma mark - UI Actions
- (IBAction)initEngineBtnClicked:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngineBtnClicked:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.chatTextQueryButton.enabled = FALSE;
}
- (IBAction)startEngineBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
// Directive
NSLog(@"Directive: SEDirectiveStartEngine");
NSString* startJson = [NSString stringWithFormat: @"{\"dialog\":{\"bot_name\":\"%@\"}}", [self.settings getString:SETTING_DIALOG_BOT_NAME]];
SEEngineErrorCode ret = [self.speechEngine sendDirective:SEDirectiveStartEngine data:startJson];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
} else if (ret == SENoError) {
self.dialogMessages = [[NSMutableArray alloc] init];
self.resultTextView.text = @"";
//
if (self.helloTextView.text.length != 0) {
[self sayHello];
}
} else {
[self.statusTextView setText:[NSString stringWithFormat: @"Fail to start engine: %d", ret]];
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
NSLog(@"Stop engine.");
// Directive
NSLog(@"Directive: SEDirectiveStopEngine");
[self.speechEngine sendDirective:SEDirectiveStopEngine];
}
- (IBAction)chatTextQueryBtnClicked:(id)sender {
if (self.speechEngine == nil) {
[self.statusTextView setText:@"Engine is not initialized!"];
return;
}
[self chatTextQuery];
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功: %d", type);
[self speechEngineStarted:strData];
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭: %d", type);
[self speechEngineStopped:strData];
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %d, data: %@", type, strData);
[self showLogMessage:strData];
break;
case SEDialogASRResponse:
[self showUserMessage: data];
break;
case SEDialogASREnded:
[self confirmUserMessage];
break;
case SEDialogChatResponse:
[self showAssistantMessage: data];
break;
case SEDialogASRInfo:
case SEDialogChatEnded:
[self confirmAssistantMessage];
break;
default:
break;
}
}
#pragma mark - SpeechEngine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.initialEngineButton.enabled = FALSE;
self.uninitialEngineButton.enabled = TRUE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.initialEngineButton.enabled = TRUE;
self.uninitialEngineButton.enabled = FALSE;
});
}
- (void)speechEngineStarted:(NSString *)sessionId {
[self showLogMessage: [NSString stringWithFormat:@"Engine start: %@", sessionId]];
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.chatTextQueryButton.enabled = TRUE;
[self.statusTextView setText:@"Engine Started!"];
});
}
- (void)speechEngineStopped:(NSString *)sessionId {
[self showLogMessage: [NSString stringWithFormat:@"Engine stop: %@", sessionId]];
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = FALSE;
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.chatTextQueryButton.enabled = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
});
}
# pragma mark - Helper
- (void)showLogMessage:(NSString *)text {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [[DialogMessage alloc] init];
message.role = ROLE_LOG;
message.text = text;
message.confirmed = true;
[self.dialogMessages addObject:message];
[self updateMessageUI];
});
}
- (void)showHelloMessage:(NSString *)helloMessage {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [[DialogMessage alloc] init];
message.role = ROLE_ASSISTANT;
message.text = helloMessage;
message.confirmed = true;
[self.dialogMessages addObject:message];
[self updateMessageUI];
});
}
- (void)showChatTextQueryMessage:(NSString *)queryMessage {
dispatch_async(dispatch_get_main_queue(), ^{
[self confirmAssistantMessage];
DialogMessage* message = [[DialogMessage alloc] init];
message.role = ROLE_USER;
message.text = queryMessage;
message.confirmed = true;
[self.dialogMessages addObject:message];
[self updateMessageUI];
});
}
- (void)showUserMessage:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_USER];
if (message == nil) {
message = [[DialogMessage alloc] init];
message.role = ROLE_USER;
message.confirmed = false;
[self.dialogMessages addObject:message];
}
// json
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
message.text = [[[jsonResult objectForKey:@"results"] firstObject] objectForKey:@"text"];
[self updateMessageUI];
});
}
- (void)confirmUserMessage {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_USER];
if (message) {
message.confirmed = true;
}
});
}
- (void)showAssistantMessage:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_ASSISTANT];
if (message == nil) {
message = [[DialogMessage alloc] init];
message.role = ROLE_ASSISTANT;
message.text = @"";
message.confirmed = false;
[self.dialogMessages addObject:message];
}
// json
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
message.text = [message.text stringByAppendingString:[jsonResult objectForKey:@"content"]];
[self updateMessageUI];
});
}
- (void)confirmAssistantMessage {
dispatch_async(dispatch_get_main_queue(), ^{
DialogMessage* message = [self lastUnconfirmedMessage:ROLE_ASSISTANT];
if (message) {
message.confirmed = true;
}
});
}
- (DialogMessage*)lastUnconfirmedMessage:(Role)role {
for (DialogMessage* message in [self.dialogMessages reverseObjectEnumerator]) {
if (message.role == role) {
if (!message.confirmed) {
return message;
}
break;
}
}
return nil;
}
- (void)updateMessageUI {
if (self.dialogMessages.count > MAX_DIALOG_MESSAGE_COUNT) {
[self.dialogMessages removeObjectAtIndex:0];
}
NSString* results = @"";
for (DialogMessage* message in self.dialogMessages) {
NSString* role = @"";
switch (message.role) {
case ROLE_USER:
role = @"[USER]:";
break;
case ROLE_ASSISTANT:
role = @"[ASSISTANT]:";
break;
case ROLE_LOG:
role = @"[LOG]:";
break;
}
results = [results stringByAppendingFormat:@"%@%@\n", role, message.text];
}
[self.resultTextView setText:results];
NSRange bottom = NSMakeRange(self.resultTextView.text.length -1, 1);
[self.resultTextView scrollRangeToVisible:bottom];
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_DIALOG forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// FulllinkViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FulllinkViewController : UIViewController
@end

View File

@ -0,0 +1,484 @@
//
// FulllinkViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "FulllinkViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface FulllinkViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@property (weak, nonatomic) IBOutlet UIButton *forceWakeupButton;
@property (weak, nonatomic) IBOutlet UIButton *cancelDialogButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long talkingFinisheTimestamp;
@property (nonatomic, assign) long startEngineTimestamp;
@property (strong, nonatomic) NSString *debugPath;
@property (strong, nonatomic) NSArray *engineNameArray;
@property (assign, nonatomic) NSInteger engineNameType;
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation FulllinkViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_FULLLINK];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
self.forceWakeupButton.enabled = FALSE;
self.cancelDialogButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[self decorateTextView:self.referTextView];
[self.referTextView setDelegate:self];
self.referTextView.editable = TRUE;
self.referTextView.text = @"高兴的反义词";
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPressTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.longPressButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
self.engineNameArray = @[SE_FULLLINK_LITE_ENGINE, SE_FULLLINK_ENGINE];
self.engineNameType = 0;
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEWakeupResult:
[self speechEngineWakeUp:data];
break;
case SEAsrPartialResult:
[self speechEngineResult:data isFinal:FALSE];
break;
case SEFinalResult:
[self speechEngineResult:data isFinal:TRUE];
break;
case SENluResult:
[self speechEngineNluResult:data];
break;
case SEVolumeLevel:
NSLog(@"volume level: %s", (char*)data.bytes);
break;
case SEEngineLog:
NSLog(@"engine log: %s", (char*)data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)InitEngine:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示语音识别结果"];
}
- (IBAction)startEngine:(id)sender {
NSLog(@"Start engine.");
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME]
forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
NSString* engine_name = [self.engineNameArray objectAtIndex:self.engineNameType];
if (engine_name == SE_FULLLINK_LITE_ENGINE) {
[self.curEngine setBoolParam:[self.settings getBool:SETTING_FULLLINK_ONLY_ASR] forKey:SE_PARAMS_KEY_FULLLINK_ASR_ONLY_BOOL];
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_FULLLINK_ASR_AUTO_STOP_BOOL];
}
[self configUserParams];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"recorder.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
NSInteger ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
- (IBAction)stopEngine:(id)sender {
NSLog(@"Stop engine.");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (IBAction)forceWakeup:(id)sender {
NSLog(@"Force wakeup.");
NSString* engine_name = [self.engineNameArray objectAtIndex:self.engineNameType];
if (engine_name == SE_FULLLINK_ENGINE) {
[self.curEngine sendDirective:SEDirectiveTriggerWakeup];
}
}
- (IBAction)cancelDialog:(id)sender {
NSLog(@"Force wakeup.");
[self.curEngine sendDirective:SEDirectiveCancelCurrentDialog];
}
- (IBAction)longPressTriggered:(UILongPressGestureRecognizer *)longPgr {
[self configUserParams];
if (longPgr.state == UIGestureRecognizerStateBegan) {
NSLog(@"Long press begin.");
[self setResultText:@""];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME]
forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
NSString* engine_name = [self.engineNameArray objectAtIndex:self.engineNameType];
if (engine_name == SE_FULLLINK_LITE_ENGINE) {
[self.curEngine setBoolParam:[self.settings getBool:SETTING_FULLLINK_ONLY_ASR] forKey:SE_PARAMS_KEY_FULLLINK_ASR_ONLY_BOOL];
}
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"recorder.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
NSInteger ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
NSLog(@"Long press ended.");
self.talkingFinisheTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
self.engineInitButton.enabled = FALSE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:[NSString stringWithFormat:@"%@/%@", self.debugPath, @"kws"] forKey:SE_PARAMS_KEY_KWS_ROOT_PATH_STRING];
[self.curEngine setStringParam:[NSString stringWithFormat:@"%@/%@", self.debugPath, @"signal"] forKey:SE_PARAMS_KEY_SIGNAL_ROOT_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setStringParam:@"robot_ios" forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:1 forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME]
forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
NSString *address = [self.settings getString:SETTING_ADDRESS];
if (!address.length) {
address = SDEF_DEFAULT_ADDRESS;
}
NSLog(@"Current address: %@", address);
[self.curEngine setStringParam:address forKey:SE_PARAMS_KEY_FULLLINK_ADDRESS_STRING];
NSString *uri = [self.settings getString:SETTING_URI];
if (!uri.length) {
uri = SDEF_FULLLINK_DEFAULT_URI;
}
NSLog(@"Current uri: %@", uri);
[self.curEngine setStringParam:uri forKey:SE_PARAMS_KEY_FULLLINK_URI_STRING];
[self.curEngine setStringParam:@"" forKey:SE_PARAMS_KEY_FULLLINK_QUERY_STRING_STRING];
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
self.engineNameType = [self.settings getOptions:SETTING_FULLLINK_ENGINE_TYPE].chooseIdx;
[self.curEngine setStringParam:[self.engineNameArray objectAtIndex:self.engineNameType] forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_FULLLINK_ENABLE_RECORDER_DUMP] forKey:@"enable_dump"];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000) {
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
}
}
NSInteger ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %ld", ret);
}
if (ret == SENoError) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed];
}
}
- (void)configUserParams {
NSString *param = @"{\"auth_key\":\"\",\"product_info\":{\"product_id\":2001},\"nlg_request\":{\"nlg_type\":\"\",\"nlg_content\":\"";
param = [param stringByAppendingString:self.referTextView.text];
param = [param stringByAppendingString:@"\"},\"session_info\":{},\"client_info\":{\"client_user_id\":\"123456\",\"client_device_id\":\"123456\",\"client_app_id\":\"111\",\"client_device_unique_code\":\"111\",\"sdk_version\":0,\"client_app_version\":\"\"}}"];
[self.curEngine setStringParam:param forKey:SE_PARAMS_KEY_FULLLINK_USER_PARAM_STRING];
}
- (NSString *)getRecorderType {
switch ([self.settings getOptions:SETTING_RECORD_TYPE].chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
self.forceWakeupButton.enabled = FALSE;
self.cancelDialogButton.enabled = FALSE;
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
[self.streamRecorder setSpeechEngine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.startEngineTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
[self setResultText:@""];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.longPressButton.enabled = FALSE;
self.forceWakeupButton.enabled = TRUE;
self.cancelDialogButton.enabled = TRUE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = TRUE;
self.forceWakeupButton.enabled = FALSE;
self.cancelDialogButton.enabled = FALSE;
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
long response_delay = 0;
if (isFinal && self.talkingFinisheTimestamp > 0) {
response_delay = [self timeDelayFrom:self.talkingFinisheTimestamp];
self.talkingFinisheTimestamp = 0;
}
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
if (![jsonResult objectForKey:@"messageData"]) {
return;
}
[text appendFormat:@"%@", [[[jsonResult objectForKey:@"messageData"] objectForKey:@"inputText"] objectForKey:@"text"]];
if (text.length) {
[self setResultText:text];
}
}
- (void)speechEngineNluResult:(NSData *)data {
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
if (![jsonResult objectForKey:@"messageData"]) {
return;
}
[text appendFormat:@"%@\n%@", [[jsonResult objectForKey:@"messageData"] objectForKey:@"inputText"], [[jsonResult objectForKey:@"messageData"] objectForKey:@"outputText"]];
if (text.length) {
[self setResultText:text];
}
bool disableTts = [self.settings getBool:SETTING_FULLLINK_DISABLE_TTS];
[self.curEngine sendDirective:SEDirectivePlayingDecision data:disableTts ? @"false" : @"true"];
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
// [self stopEngine:nil];
});
}
- (void)speechEngineWakeUp:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_FULLLINK forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// AsrOfflineViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface KwsViewController : UIViewController
@end

View File

@ -0,0 +1,482 @@
//
// KwsViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "KwsViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SpeechResourceManager.h"
#import "SensitiveDefines.h"
@interface KwsViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation KwsViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_KWS];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPressTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.longPressButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams{
//Engine Name
[self.curEngine setStringParam:SE_KWS_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//
[self.curEngine setStringParam:[self.settings getOptionsValue:SETTING_RECORD_TYPE] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
// RECORDER_TYPE_STREAM 16K
if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_STREAM) {
if ([self.streamRecorder getSampleRate] != 16000) {
// RECORDER_TYPE_STREAM
// SDK
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
// APP
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
}
}
NSString* recPath = @"";
if ([self.settings getBool:SETTING_RECORD_SAVE]) {
recPath = self.debugPath;
}
[self.curEngine setStringParam:recPath forKey:SE_PARAMS_KEY_REC_PATH_STRING];
[self.curEngine setIntParam:[self.settings getOptions:SETTING_RECORD_FILE_TYPE].chooseIdx forKey:SE_PARAMS_KEY_REC_FILE_TYPE_INT];
[self.curEngine setStringParam:@"{\"array_type\": 0,\"radius\":0.0,\"total_channels\":1,\"mic_offset\":0,\"mic_num\":1,\"ref_offset\":0,\"ref_num\":0,\"vad_speech_active_thresh\":0.5,\"vad_speech_deactive_thresh\":0.5}" forKey:SE_PARAMS_KEY_KWS_USER_PARAM_STRING];
[self.curEngine setIntParam:600 forKey:SE_PARAMS_KEY_KWS_VAD_END_SILENCE_TIMEOUT_INT];
[self.curEngine setBoolParam:false forKey:SE_PARAMS_KEY_KWS_ENABLE_VAD_BOOL];
SpeechResourceManager *resourceManager = [SpeechResourceManager shareInstance];
NSString* modelsPath = [resourceManager getModelPath:[self getKwsModelName]];
NSLog(@"Kws model path: %@", modelsPath);
[self.curEngine setStringParam:modelsPath forKey:SE_PARAMS_KEY_KWS_ROOT_PATH_STRING];
}
-(void)configStartParams{
// APP
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME] forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_STREAM) {
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
}
} else if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_FILE) {
// 使
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"kws_rec_file.pcm"];
NSLog(@"输入的音频文件路径: %@", file_path);
// 使
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
}
#pragma mark - Init Methods
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径: %@", self.debugPath);
SpeechResourceManager *resourceManager = [SpeechResourceManager shareInstance];
[resourceManager setAppId:SDEF_APPID];
[resourceManager setAppVersion:@"1.0.0"];
[resourceManager setDeviceId:self.deviceID];
[resourceManager setRootPath:self.debugPath];
[resourceManager setSpeechEngineName:SE_KWS_ENGINE];
[resourceManager setAddress:@"https://sdk.bytespeech.com"];
[resourceManager setOnlineModelEnable:NO];
[resourceManager setup];
if ([resourceManager checkModelExist:[self getKwsModelName]]) {
[resourceManager checkModelVersion:[self getKwsModelName] completion:^(SEResourceStatus status, BOOL needUpdate, NSData *data) {
if (status == kSERSuccess) {
if (needUpdate) {
[self fetchResource];
} else {
[self initEngineInternal];
}
} else {
NSLog(@"Model check failed: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[self.resultTextView setText:@"Failed to check asr resource"];
[self speechEngineInitFailed:status];
}
}];
} else {
[self fetchResource];
}
}
- (void)fetchResource {
SpeechResourceManager *resourceManager = [SpeechResourceManager shareInstance];
[resourceManager fetchModelByName:[self getKwsModelName] completion:^(SEResourceStatus status, NSData* data) {
if (status == kSERSuccess) {
[self initEngineInternal];
} else {
[self.resultTextView setText:@"Failed to fetch asr resource"];
[self speechEngineInitFailed:status];
}
}];
}
- (void)initEngineInternal {
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret == SENoError) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed:ret];
}
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
}
- (void)speechEngineInitSucceeded {
[self.streamRecorder setSpeechEngine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
#pragma mark - UI Actions
- (IBAction)initEngineBtnClicked:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngineBtnClicked:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示语音唤醒结果"];
}
- (IBAction)startEngineBtnClicked:(id)sender {
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartParams];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
NSLog(@"启用引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)recordTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
[self setResultText:@""];
NSLog(@"配置启动参数");
[self configStartParams];
// DirectiveSYNC_STOP
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
// Directive
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
// Directive
NSLog(@"Directive: SEDirectiveFinishTalking");
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_STREAM) {
[self.streamRecorder stop];
}
}
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功");
[self speechEngineStarted];
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭");
[self speechEngineStopped];
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEWakeupResult:
// Callback:
NSLog(@"Callback: 唤醒结果");
[self speechEngineResult:data];
break;
case SEFinalResult:
case SEVolumeLevel:
// Callback:
NSLog(@"Callback: 录音音量");
break;
default:
break;
}
}
#pragma mark - Engine Callback
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.longPressButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self getRecorderType] == SE_RECORDER_TYPE_STREAM) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
// json
id error_json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
if ([error_json isKindOfClass:[NSDictionary class]]) {
NSDictionary *error_info = error_json;
NSInteger error_code = [[error_info objectForKey:@"err_code"] intValue];
switch (error_code) {
case SEEncodingAudioError:
[self stopEngineBtnClicked:nil];
break;
default:
break;
}
// UI
if ([error_info objectForKey:@"name"] != nil) {
NSString* error_msg = [[error_json objectForKey:@"err_msg"] stringValue];
NSString* reqid = [[error_json objectForKey:@"reqid"] stringValue];
[self setResultText:[NSString stringWithFormat:@"reqid: %@, error: %@", reqid, error_msg]];
} else {
[self setResultText:[NSString stringWithFormat:@"%@", error_info]];
}
}
});
}
#pragma mark - Helper
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
- (void)updateWakeupWords {
if (self.curEngine == nil) {
return;
}
NSString* customWords = [self.settings getString:SETTING_KWS_CUSTOM_WORDS];
if (customWords.length == 0) {
return;
}
[self.curEngine sendDirective:SEDirectiveUpdateWakeupWordsParams data:customWords];
}
- (NSString*)getKwsModelName {
return [self.settings getString:SETTING_KWS_MODEL_NAME];
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_KWS forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>2</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>Speech demo would like to access your recorder data.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
//
// TtsNormalViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TtsNormalViewController : UIViewController
@end

View File

@ -0,0 +1,824 @@
//
// TtsNormalViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "TtsNormalViewController.h"
#include <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface TtsNormalViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *createConnectionButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *pauseResumeButton;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;
// Engine State
@property (assign, nonatomic) BOOL engineInited;
@property (assign, nonatomic) BOOL connectionCreated;
@property (assign, nonatomic) BOOL engineStarted;
@property (assign, nonatomic) BOOL engineErrorOccurred;
@property (assign, nonatomic) BOOL playerPaused;
// Settings
@property (strong, nonatomic) Settings *settings;
// 线
@property (strong, nonatomic) NSString *ttsAppId;
@property (strong, nonatomic) NSString *ttsVoiceOnline;
@property (strong, nonatomic) NSString *ttsVoiceTypeOnline;
// 线
@property (strong, nonatomic) NSString *ttsVoiceOffline;
@property (strong, nonatomic) NSString *ttsVoiceTypeOffline;
//
@property (strong, nonatomic) NSString *ttsText;
@end
@implementation TtsNormalViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_TTS];
self.engineSwitchButton.enabled = TRUE;
[self decorateTextView:self.referTextView];
[self decorateTextView:self.resultTextView];
[self.referTextView setDelegate:self];
self.referTextView.editable = TRUE;
self.engineInited = FALSE;
self.connectionCreated = FALSE;
self.engineStarted = FALSE;
self.engineErrorOccurred = FALSE;
self.playerPaused = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Notifications
- (void)appDidEnterBackground:(UIApplication *)application; {
NSLog(@"app enter background.");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
-(void)appWillTerminate:(NSNotification*)note {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams {
//Engine Name
[self.curEngine setStringParam:SE_TTS_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Work Mode,
// SETtsWorkModeOnline, 线线
// SETtsWorkModeOffline, 线线
// SETtsWorkModeAlternate, 线线
[self.curEngine setIntParam:[self getTtsWorkMode] forKey:SE_PARAMS_KEY_TTS_WORK_MODE_INT];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//User ID线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
// true PARAMS_KEY_TTS_AUDIO_PATH_STRING
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_DUMP]
forKey:SE_PARAMS_KEY_TTS_ENABLE_DUMP_BOOL];
// TTS APP 访 tts_{reqid}.wav, {reqid} id
// PARAMS_KEY_TTS_ENABLE_DUMP_BOOL true
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_TTS_AUDIO_PATH_STRING];
// 24000
[self.curEngine setIntParam:[self.settings getInt:SETTING_TTS_SAMPLE_RATE] forKey:SE_PARAMS_KEY_TTS_SAMPLE_RATE_INT];
//使 0
[self.curEngine setIntParam:[self.settings getInt:SETTING_AUDIO_FADEOUT_DURATION] forKey:SE_PARAMS_KEY_AUDIO_FADEOUT_DURATION_INT];
//使 SDK false. SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL true
[self.curEngine setBoolParam:[self.settings getBool:SETTING_PREVENT_PLAYER_CREATION] forKey:SE_PARAMS_KEY_PREVENT_PLAYER_CREATION_BOOL];
// ------------------------ 线 -----------------------
NSString* appid = [self.settings getString:SETTING_APPID];
self.ttsAppId = appid.length > 0 ? appid : SDEF_APPID;
//线Appid
[self.curEngine setStringParam:self.ttsAppId forKey:SE_PARAMS_KEY_APP_ID_STRING];
NSString* token = [self.settings getString:SETTING_TOKEN];
NSString* ttsAppToken = token.length > 0 ? token : SDEF_TOKEN;
//线Token
[self.curEngine setStringParam:ttsAppToken forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//
NSString *address = [self.settings getString:SETTING_ADDRESS];
NSString *ttsAddress = address.length > 0 ? address : SDEF_DEFAULT_ADDRESS;
[self.curEngine setStringParam:ttsAddress forKey:SE_PARAMS_KEY_TTS_ADDRESS_STRING];
//Uri
NSString *uri = [self.settings getString:SETTING_URI];
NSString *ttsUri = uri.length > 0 ? uri : SDEF_TTS_DEFAULT_URI;
[self.curEngine setStringParam:ttsUri forKey:SE_PARAMS_KEY_TTS_URI_STRING];
// websocket
[self.curEngine setBoolParam:![self.settings getBool:SETTING_DISABLE_WS_RECONNECT] forKey:SE_PARAMS_KEY_ENABLE_WS_RECONNECT_BOOL];
//线 opus-ogg
[self.curEngine setIntParam:10 forKey:SE_PARAMS_KEY_TTS_COMPRESSION_RATE_INT];
// ------------------------ 线 -----------------------
if ([self getTtsWorkMode] != SETtsWorkModeOnline && [self getTtsWorkMode] != SETtsWorkModeFile) {
NSString* resourcePath = @"";
if ([[self.settings getOptionsValue:SETTING_TTS_OFFLINE_RESOURCE_FORMAT] isEqual: @"SingleVoice"]) {
resourcePath = [[SpeechResourceManager shareInstance] getModelPath];
} else if ([[self.settings getOptionsValue:SETTING_TTS_OFFLINE_RESOURCE_FORMAT] isEqual: @"MultipleVoice"]) {
NSString *model_name = [self.settings getString:SETTING_TTS_MODEL_NAME];
resourcePath = [[SpeechResourceManager shareInstance] getModelPath:model_name];
}
NSLog(@"TTS resource root path: %@", resourcePath);
//线
[self.curEngine setStringParam:resourcePath forKey:SE_PARAMS_KEY_TTS_OFF_RESOURCE_PATH_STRING];
}
//线
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_LICENSE_DIRECTORY_STRING];
NSString* authenticationType = [self getAuthenticationType];
//Authenticate Type
[self.curEngine setStringParam:authenticationType forKey:SE_PARAMS_KEY_AUTHENTICATE_TYPE_STRING];
if ([authenticationType isEqualToString:SE_AUTHENTICATE_TYPE_PRE_BIND]) {
// APP 使线
NSString *licenseName = [self.settings getString:SETTING_LICENSE_NAME];
NSString *licenseBusiId = [self.settings getString:SETTING_LICENSE_BUSI_ID];
// ID, 线使,
//
[self.curEngine setStringParam:licenseName forKey:SE_PARAMS_KEY_LICENSE_NAME_STRING];
// ID,
[self.curEngine setStringParam:licenseBusiId forKey:SE_PARAMS_KEY_LICENSE_BUSI_ID_STRING];
} else if ([authenticationType isEqualToString:SE_AUTHENTICATE_TYPE_LATE_BIND]) {
// APP 使使线
//线Authenticate Address
[self.curEngine setStringParam:SDEF_AUTHENTICATE_ADDRESS forKey:SE_PARAMS_KEY_AUTHENTICATE_ADDRESS_STRING];
//线Authenticate Uri
[self.curEngine setStringParam:SDEF_AUTHENTICATE_URI forKey:SE_PARAMS_KEY_AUTHENTICATE_URI_STRING];
NSString* curBusinessKey = [self.settings getString:SETTING_BUSINESS_KEY];
NSString* curAuthenticateSecret = [self.settings getString:SETTING_AUTHENTICATE_SECRET];
//线Business Key
[self.curEngine setStringParam:curBusinessKey forKey:SE_PARAMS_KEY_BUSINESS_KEY_STRING];
//线Authenticate Secret
[self.curEngine setStringParam:curAuthenticateSecret forKey:SE_PARAMS_KEY_AUTHENTICATE_SECRET_STRING];
}
// ------------------------ 线 -----------------------
if ([self getTtsWorkMode] == SETtsWorkModeAlternate) {
// 线线SDK
// 线线
//线
[self.curEngine setIntParam:30 forKey:SE_PARAMS_KEY_TTS_FADEIN_DURATION_INT];
//线
[self.curEngine setIntParam:30 forKey:SE_PARAMS_KEY_TTS_FADEOUT_DURATION_INT];
}
}
-(void)configStartTtsParams {
//TTS 使
[self.curEngine setStringParam:SE_TTS_SCENARIO_TYPE_NORMAL forKey:SE_PARAMS_KEY_TTS_SCENARIO_STRING];
NSString* curText = self.referTextView.text;
if (curText.length > 0) {
self.ttsText = curText;
} else {
self.ttsText = @"愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者流的话。能做事的做事,能发声的发声。有一分热,发一分光。就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。此后如竟没有炬火:我便是唯一的光。";
}
// 80
[self.curEngine setStringParam:self.ttsText forKey:SE_PARAMS_KEY_TTS_TEXT_STRING];
//(TTS_TEXT_TYPE_PLAIN) SSML (TTS_TEXT_TYPE_SSML)
[self.curEngine setStringParam:[self getTtsTextType] forKey:SE_PARAMS_KEY_TTS_TEXT_TYPE_STRING];
// TTS //线SDK/
[self.curEngine setDoubleParam:[self.settings getDouble:SETTING_TTS_SPEAK_SPEED] forKey:SE_PARAMS_KEY_TTS_SPEED_RATIO_DOUBLE];
// TTS //线SDK/
[self.curEngine setDoubleParam:[self.settings getDouble:SETTING_TTS_AUDIO_VOLUME] forKey:SE_PARAMS_KEY_TTS_VOLUME_RATIO_DOUBLE];
// TTS //线SDK/
[self.curEngine setDoubleParam:[self.settings getDouble:SETTING_TTS_AUDIO_PITCH] forKey:SE_PARAMS_KEY_TTS_PITCH_RATIO_DOUBLE];
// 0ms
[self.curEngine setIntParam:[self.settings getInt:SETTING_TTS_SILENCE_DURATION] forKey:SE_PARAMS_KEY_TTS_SILENCE_DURATION_INT];
//使 SDK true
[self.curEngine setBoolParam:![self.settings getBool:SETTING_PREVENT_PLAYER_CREATION] && [self.settings getBool:SETTING_TTS_ENABLE_PLAYER]
forKey:SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL];
// SDK
// SDK SETtsAudioData
[self.curEngine setIntParam:[self.settings getBool:SETTING_TTS_ENABLE_DATA_CALLBACK] ? SETtsDataCallbackModeAll : SETtsDataCallbackModeNone forKey:SE_PARAMS_KEY_TTS_DATA_CALLBACK_MODE_INT];
// SDK 使 reqid
NSString* ttsReqId = [self.settings getString:SETTING_TTS_REQUEST_ID];
if (ttsReqId.length > 0) {
NSLog(@"Tts req id: %@", ttsReqId);
// reqid, MESSAGE_TYPE_TTS_SYNTHESIS_BEGIN
[self.curEngine setStringParam:ttsReqId forKey:SE_PARAMS_KEY_TTS_REQUEST_ID_STRING];
}
// ------------------------ 线 -----------------------
//
NSString *cluster = [self.settings getString:SETTING_CLUSTER];
[self.curEngine setStringParam:cluster forKey:SE_PARAMS_KEY_TTS_CLUSTER_STRING];
NSString *voiceOnline = [self.settings getString:SETTING_ONLINE_VOICE];
if (voiceOnline.length <= 0) {
voiceOnline = [self.settings getOptionsValue:SETTING_ONLINE_VOICE];
}
self.ttsVoiceOnline = voiceOnline;
//线使
[self.curEngine setStringParam:self.ttsVoiceOnline forKey:SE_PARAMS_KEY_TTS_VOICE_ONLINE_STRING];
NSString *voiceTypeOnline = [self.settings getString:SETTING_ONLINE_VOICE_TYPE];
if (voiceTypeOnline.length <= 0) {
voiceTypeOnline = [self.settings getOptionsValue:SETTING_ONLINE_VOICE_TYPE];
}
self.ttsVoiceTypeOnline = voiceTypeOnline;
//线使
[self.curEngine setStringParam:self.ttsVoiceTypeOnline forKey:SE_PARAMS_KEY_TTS_VOICE_TYPE_ONLINE_STRING];
//线
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_CACHE] forKey:SE_PARAMS_KEY_TTS_ENABLE_CACHE_BOOL];
//线
[self.curEngine setStringParam:[self.settings getString:SETTING_TTS_ONLINE_LANGUAGE] forKey:SE_PARAMS_KEY_TTS_LANGUAGE_ONLINE_STRING];
//线
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_WITH_INTENT] forKey:SE_PARAMS_KEY_TTS_WITH_INTENT_BOOL];
//线 happy, sad
[self.curEngine setStringParam:[self.settings getString:SETTING_TTS_EMOTION] forKey:SE_PARAMS_KEY_TTS_EMOTION_STRING];
// 1, 0
[self.curEngine setIntParam:[self.settings getBool:SETTING_TTS_ENABLE_RESUME_FROM_BREAKPOINT] forKey:SE_PARAMS_KEY_TTS_WITH_FRONTEND_INT];
//使
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_USE_VOICECLONE_VOICE] forKey:SE_PARAMS_KEY_TTS_USE_VOICECLONE_BOOL];
//使
[self.curEngine setStringParam:[self.settings getString:SETTING_TTS_BACKEND_CLUSTER] forKey:SE_PARAMS_KEY_TTS_BACKEND_CLUSTER_STRING];
//线JSON SDK
[self.curEngine setStringParam:[self.settings getString:SETTING_TTS_REQUEST_PARAMS] forKey:SE_PARAMS_KEY_TTS_REQ_PARAMS_STRING];
// ------------------------ 线 -----------------------
NSString *voiceOffline = [self.settings getString:SETTING_OFFLINE_VOICE];
if (voiceOffline.length <= 0) {
voiceOffline = [self.settings getOptionsValue:SETTING_OFFLINE_VOICE];
}
self.ttsVoiceOffline = voiceOffline;
//线使
[self.curEngine setStringParam:self.ttsVoiceOffline forKey:SE_PARAMS_KEY_TTS_VOICE_OFFLINE_STRING];
NSString *voiceTypeOffline = [self.settings getString:SETTING_OFFLINE_VOICE_TYPE];
if (voiceTypeOffline.length <= 0) {
voiceTypeOffline = [self.settings getOptionsValue:SETTING_OFFLINE_VOICE_TYPE];
}
self.ttsVoiceTypeOffline = voiceTypeOffline;
//线使
[self.curEngine setStringParam:self.ttsVoiceTypeOffline forKey:SE_PARAMS_KEY_TTS_VOICE_TYPE_OFFLINE_STRING];
//线 CPU
// 使线CPU
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_LIMIT_CPU_USAGE] forKey:SE_PARAMS_KEY_TTS_LIMIT_CPU_USAGE_BOOL];
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
if ([self getTtsWorkMode] == SETtsWorkModeOnline || [self getTtsWorkMode] == SETtsWorkModeFile) {
// 使线线
[self initEngineInternal];
} else {
[self.statusTextView setText:@"Waiting for loading model."];
// 线
if ([[self.settings getOptionsValue:SETTING_TTS_OFFLINE_RESOURCE_FORMAT] isEqual: @"MultipleVoice"]) {
// 线(V2)线
NSLog(@"当前所用资源类别为多音色资源,开始准备多音色资源");
[self prepareMultipleVoiceResource];
} else if ([[self.settings getOptionsValue:SETTING_TTS_OFFLINE_RESOURCE_FORMAT] isEqual: @"SingleVoice"]) {
// 线(V4 )线
NSLog(@"当前所用资源类别为单音色资源,开始准备单音色资源");
[self prepareSingleVoiceResource];
}
}
}
- (void)prepareMultipleVoiceResource {
//
//
NSString *model_name = [self.settings getString:SETTING_TTS_MODEL_NAME];
SpeechResourceManager *speechResourceManager = [SpeechResourceManager shareInstance];
NSLog(@"检查本地是否存在可用模型");
if (![speechResourceManager checkModelExist:model_name]) {
NSLog(@"本地没有模型,开始下载");
[self fetchMultipleVoiceResource:model_name];
} else {
NSLog(@"模型存在,检查是否需要更新模型");
[speechResourceManager checkModelVersion:model_name completion:^(SEResourceStatus status, BOOL needUpdate, NSData *data) {
if (status != kSERSuccess || needUpdate == NO) {
NSLog(@"无需更新,直接使用本地已有模型。");
[self initEngineInternal];
} else {
NSLog(@"存在更新,开始下载模型");
[self fetchMultipleVoiceResource:model_name];
}
}];
}
}
- (void)fetchMultipleVoiceResource:(NSString*)model_name {
NSLog(@"需要下载的模型名为 %@", model_name);
SpeechResourceManager *speechResourceManager = [SpeechResourceManager shareInstance];
[speechResourceManager fetchModelByName:model_name completion:^(SEResourceStatus status, NSData* data) {
if (status == kSERSuccess) {
NSLog(@"下载成功");
[self initEngineInternal];
} else {
NSLog(@"下载失败,错误码: %d", status);
[self speechEngineInitFailed:kSERDownloadFailed];
}
}];
}
- (void)prepareSingleVoiceResource {
SpeechResourceManager *speechResourceManager = [SpeechResourceManager shareInstance];
NSString* offlineLanguage = [self.settings getString:SETTING_TTS_OFFLINE_LANGUAGE];
if (offlineLanguage.length <= 0) {
offlineLanguage = SDEF_TTS_DEFAULT_OFFLINE_LANGUAGE;
}
NSArray* ttsLanguageArray = @[offlineLanguage];
NSLog(@"需要下载的离线合成语种资源有: %@", ttsLanguageArray);
[speechResourceManager setTtsLanguage:ttsLanguageArray];
NSArray* needDownloadVoiceType = (NSArray *)SDEF_TTS_DEFAULT_DOWNLOAD_OFFLINE_VOICES();
NSArray* voiceTypeArray = [self.settings getOptions:SETTING_OFFLINE_VOICE_TYPE].optionsArray;
if (voiceTypeArray != nil && voiceTypeArray.count > 0) {
needDownloadVoiceType = voiceTypeArray;
}
NSLog(@"需要下载的离线合成音色资源有: %@", needDownloadVoiceType);
[speechResourceManager setTtsVoiceType:needDownloadVoiceType];
NSLog(@"检查本地是否存在可用模型");
if ([speechResourceManager checkModelExist]) {
NSLog(@"本地没有模型,开始下载");
[self fetchSingleVoiceResource];
} else {
NSLog(@"模型存在,检查是否需要更新模型");
[speechResourceManager checkModelVersion:^(SEResourceStatus status, BOOL needUpdate, NSData *data) {
if (status != kSERSuccess || needUpdate == NO) {
NSLog(@"无需更新,直接使用本地已有模型。");
[self initEngineInternal];
} else {
NSLog(@"存在更新,开始下载模型");
[self fetchSingleVoiceResource];
}
}];
}
}
- (void)fetchSingleVoiceResource {
SpeechResourceManager *speechResourceManager = [SpeechResourceManager shareInstance];
[speechResourceManager fetchModel:^(SEResourceStatus status, NSData* data) {
if (status == kSERSuccess) {
NSLog(@"下载成功");
[self initEngineInternal];
} else {
NSLog(@"下载失败,错误码: %d", status);
[self speechEngineInitFailed:kSERDownloadFailed];
}
}];
}
- (void)initEngineInternal {
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
self.engineInited = (ret == SENoError);
if (self.engineInited) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed:ret];
}
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
}
#pragma mark - UI Actions
- (IBAction)switchEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self clearResult:nil];
self.startEngineButton.enabled = FALSE;
self.pauseResumeButton.enabled = FALSE;
if (self.engineInited) {
self.referTextView.editable = FALSE;
[self uninitEngine];
self.engineInited = FALSE;
self.connectionCreated = FALSE;
[self.statusTextView setText:@"Waiting for init."];
self.engineSwitchButton.enabled = TRUE;
[self.engineSwitchButton setTitle:@"Init Engine" forState:UIControlStateNormal];
self.stopEngineButton.enabled = FALSE;
self.createConnectionButton.enabled = FALSE;
} else {
self.referTextView.editable = TRUE;
[self initEngine];
}
}
- (IBAction)createConnection:(id)sender {
if (self.connectionCreated) {
NSLog(@"Connection is created.");
return;
}
// SEDirectiveCreateConnection 线使
// SEDirectiveCreateConnection
// 使 SEDirectiveCreateConnection DIRECTIVE_START_ENGINE
NSLog(@"触发提前建连");
NSLog(@"Directive: SEDirectiveCreateConnection");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveCreateConnection];
if(ret != SENoError) {
NSString* error_message = [NSString stringWithFormat:@"在线合成提前建连失败: %d", ret];
NSLog(@"%@", error_message);
[self createConnectionFailed:error_message];
} else {
NSString* message = [NSString stringWithFormat:@"在线合成提前建连成功: %d", ret];
NSLog(@"%@", message);
[self createConnectionSucceeded:message];
}
}
- (IBAction)startEngineBtnClicked:(id)sender {
NSLog(@"Start engine, current status: %d", self.engineStarted);
if (!self.engineStarted) {
[self clearResult:nil];
self.engineErrorOccurred = FALSE;
// DirectiveSYNC_STOP
NSLog(@"关闭引擎(同步)");
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
[self configStartTtsParams];
NSLog(@"启动引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (SENoError != ret) {
NSString* message = [NSString stringWithFormat:@"发送启动引擎指令失败: %d", ret];
[self sendStartEngineDirectiveFailed:message];
}
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void) pausePlayback {
NSLog(@"暂停播放");
NSLog(@"Directive: SEDirectivePausePlayer");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectivePausePlayer];
if (ret == SENoError) {
self.playerPaused = TRUE;
[self.pauseResumeButton setTitle:@"Resume" forState:UIControlStateNormal];
}
NSLog(@"Pause playback status: %d", ret);
}
- (void)resumePlayback {
NSLog(@"继续播放");
NSLog(@"Directive: SEDirectiveResumePlayer");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveResumePlayer];
if (ret == SENoError) {
self.playerPaused = FALSE;
[self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
}
NSLog(@"Resume playback status: %d", ret);
}
- (IBAction)controlPlayingStatus:(id)sender {
NSLog(@"Pause or resume player, current player status: %hhd", self.playerPaused);
if (self.playerPaused) {
[self resumePlayback];
} else {
[self pausePlayback];
}
}
- (IBAction)clearResult:(id)sender {
[self.resultTextView setText:@""];
}
#pragma mark - Message Callback
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
NSLog(@"Callback: 引擎启动成功: data: %@", data);
[self speechEngineStarted];
break;
case SEEngineStop:
NSLog(@"Callback: 引擎关闭: data: %@", data);
[self speechEngineStopped];
break;
case SEEngineError:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SETtsSynthesisBegin:
NSLog(@"Callback: 合成开始: %@", data);
[self speechStartSynthesis:data];
break;
case SETtsSynthesisEnd:
NSLog(@"Callback: 合成结束: %@", data);
[self speechFinishSynthesis:data];
break;
case SETtsStartPlaying:
NSLog(@"Callback: 播放开始: %@", data);
[self speechStartPlaying:data];
break;
case SETtsPlaybackProgress:
NSLog(@"Callback: 播放进度");
[self updatePlayingProgress:data];
break;
case SETtsFinishPlaying:
NSLog(@"Callback: 播放结束: %@", data);
[self speechFinishPlaying:data];
break;
case SETtsAudioData:
NSLog(@"Callback: 音频数据,长度 %lu 字节", (unsigned long)data.length);
[self speechTtsAudioData:data];
break;
default:
break;
}
}
- (void)speechEngineInitSucceeded {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineSwitchButton.enabled = TRUE;
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@.", self.deviceID]];
self.referTextView.editable = TRUE;
self.startEngineButton.enabled = TRUE;
self.createConnectionButton.enabled = [self getTtsWorkMode] != SETtsWorkModeOffline; });
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.engineSwitchButton.enabled = TRUE;
});
}
- (void)createConnectionSucceeded:(NSString*)tipText {
dispatch_async(dispatch_get_main_queue(), ^{
self.createConnectionButton.enabled = FALSE;
[self.resultTextView setText:tipText];
self.connectionCreated = TRUE;
});
}
- (void)createConnectionFailed:(NSString*)tipText {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:tipText];
self.connectionCreated = FALSE;
});
}
- (void)sendStartEngineDirectiveFailed:(NSString*)tipText {
NSLog(@"%@", tipText);
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:tipText];
self.engineStarted = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.referTextView.editable = FALSE;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
[self.resultTextView setText:self.ttsText];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.createConnectionButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
self.referTextView.editable = TRUE;
self.engineStarted = FALSE;
self.connectionCreated = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.createConnectionButton.enabled = [self getTtsWorkMode] != SETtsWorkModeOffline;
[self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
self.pauseResumeButton.enabled = FALSE;
self.playerPaused = FALSE;
});
}
- (void)speechEngineError:(NSData *)data {
self.engineErrorOccurred = TRUE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setTextColor:[UIColor blackColor]];
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
});
}
- (void)speechStartSynthesis:(NSData *)data {
}
- (void)speechFinishSynthesis:(NSData *)data {
}
- (void)speechStartPlaying:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
self.pauseResumeButton.enabled = TRUE;
});
}
- (void)updatePlayingProgress :(NSData *)data {
if (data != nil) {
NSError *error = nil;
id object = [NSJSONSerialization
JSONObjectWithData:data
options:0
error:&error];
if(error) {
NSLog(@"Parse data as json error!");
return ;
}
if([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *results = object;
float percentage = [[results valueForKey:@"progress"] floatValue];
NSString *reqid = [results valueForKey:@"reqid"];
NSLog(@"当前播放的文本对应的 reqid: %@,播放进度:%.3f", reqid, percentage);
}
}
}
- (void)speechFinishPlaying :(NSData *)data {
}
- (void)speechTtsAudioData:(NSData *)data {
}
#pragma mark - Helper
- (NSString*)getTtsTextType {
switch ([self.settings getOptions:SETTING_TTS_TEXT_TYPE].chooseIdx) {
case 0:
return SE_TTS_TEXT_TYPE_PLAIN;
case 1:
return SE_TTS_TEXT_TYPE_SSML;
default:
break;
}
return SE_TTS_TEXT_TYPE_PLAIN;;
}
- (int)getTtsWorkMode {
switch ([self.settings getOptions:SETTING_TTS_WORK_MODE].chooseIdx) {
case 0:
return SETtsWorkModeOnline;
case 1:
return SETtsWorkModeOffline;
case 2:
return SETtsWorkModeAlternate;
default:
break;
}
return SETtsWorkModeOnline;;
}
- (NSString*)getAuthenticationType {
switch ([self.settings getOptions:SETTING_AUTHENTICATION_TYPE].chooseIdx) {
case 0:
return SE_AUTHENTICATE_TYPE_PRE_BIND;
case 1:
return SE_AUTHENTICATE_TYPE_LATE_BIND;
default:
break;
}
return SE_AUTHENTICATE_TYPE_PRE_BIND;
}
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_TTS forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// TtsNovelViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TtsNovelViewController : UIViewController
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
//
// UniTTSViewController.h
// SpeechDemo
//
// Created by ByteDance on 2025/7/3.
// Copyright © 2025 ByteDance. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UniTTSViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,476 @@
//
// UniTTSViewController.m
// SpeechDemo
//
// Created by ByteDance on 2025/7/3.
// Copyright © 2025 ByteDance. All rights reserved.
//
#import "UniTTSViewController.h"
#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc.h>
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface UniTTSViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *pauseResumeButton;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;
// Engine State
@property (assign, nonatomic) BOOL engineInited;
@property (assign, nonatomic) BOOL engineStarted;
@property (assign, nonatomic) BOOL playerPaused;
// Settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation UniTTSViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_UNITTS];
self.engineSwitchButton.enabled = TRUE;
self.referTextView.editable = TRUE;
self.engineInited = FALSE;
self.engineStarted = FALSE;
self.playerPaused = FALSE;
[self.referTextView setText:@"愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者流的话。能做事的做事,能发声的发声。有一分热,发一分光。就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。此后如竟没有炬火:我便是唯一的光。"];
[self.statusTextView setText:@"Waiting for init."];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径 %@", self.debugPath);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(audioInterruptionHandler:)
name:AVAudioSessionInterruptionNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
#pragma mark - Notifications
-(void)appWillTerminate:(NSNotification*)note {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVAudioSessionInterruptionNotification
object:nil];
}
- (void)audioInterruptionHandler:(NSNotification*)notification {
AVAudioSessionInterruptionType interruptionType = (AVAudioSessionInterruptionType)[[notification.userInfo objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
AVAudioSessionInterruptionOptions intertuptionOptions = [[notification.userInfo objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntValue];
NSLog(@"Receive audio interruption notification, type: %lu, options: %lu.", (unsigned long)interruptionType, (unsigned long)intertuptionOptions);
if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
NSLog(@"Audio session interruption began");
@synchronized (self) {
[self pausePlayback];
}
} else if (interruptionType == AVAudioSessionInterruptionTypeEnded) {
@synchronized (self) {
NSLog(@"Audio session interruption ended");
if (intertuptionOptions == AVAudioSessionInterruptionOptionShouldResume) {
AVAudioSession *session = [AVAudioSession sharedInstance];
AVAudioSessionCategoryOptions cur_options = session.categoryOptions;
// AudioQueueStart() will return AVAudioSessionErrorCodeCannotInterruptOthers if options didn't contains AVAudioSessionCategoryOptionMixWithOthers
if (!(cur_options & AVAudioSessionCategoryOptionMixWithOthers)) {
AVAudioSessionCategoryOptions readyOptions = AVAudioSessionCategoryOptionMixWithOthers | cur_options;
[session setCategory:AVAudioSessionCategoryPlayback withOptions:readyOptions error:nil];
}
[self resumePlayback];
cur_options = session.categoryOptions;
// Remove AVAudioSessionCategoryOptionMixWithOthers, or the playback will not be interrupted any more
if (cur_options & AVAudioSessionCategoryOptionMixWithOthers) {
[session setCategory:AVAudioSessionCategoryPlayback withOptions:((~AVAudioSessionCategoryOptionMixWithOthers) & cur_options) error:nil];
}
}
}
}
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams {
//Engine NameTTS Engine TTS
[self.curEngine setStringParam:SE_BITTS_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//User ID线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
// true PARAMS_KEY_TTS_AUDIO_PATH_STRING
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_DUMP]
forKey:SE_PARAMS_KEY_TTS_ENABLE_DUMP_BOOL];
// TTS APP 访 tts_{reqid}.wav, {reqid} id
// PARAMS_KEY_TTS_ENABLE_DUMP_BOOL true
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_TTS_AUDIO_PATH_STRING];
//Header
[self.curEngine setStringParam:[self.settings getString:SETTING_REQUEST_HEADERS] forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING];
//使 SDK true
[self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_PLAYER]
forKey:SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL];
// SDK
// SDK SETtsAudioData
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK] ? SETtsDataCallbackModeAll : SETtsDataCallbackModeNone forKey:SE_PARAMS_KEY_ENABLE_PLAYER_AUDIO_CALLBACK_BOOL];
// ------------------------ 线 -----------------------
//线Appid
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
//线Token
[self.curEngine setStringParam:[self.settings getString:SETTING_TOKEN] forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
//
[self.curEngine setStringParam:[self.settings getString:SETTING_ADDRESS] forKey:SE_PARAMS_KEY_TTS_ADDRESS_STRING];
//Uri
[self.curEngine setStringParam:[self.settings getString:SETTING_URI] forKey:SE_PARAMS_KEY_TTS_URI_STRING];
//id
[self.curEngine setStringParam:[self.settings getString:SETTING_RESOURCE_ID] forKey: SE_PARAMS_KEY_RESOURCE_ID_STRING];
//TTS
[self.curEngine setIntParam:10000 forKey:SE_PARAMS_KEY_TTS_CONN_TIMEOUT_INT];
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
[self initEngineInternal];
}
- (void)initEngineInternal {
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
self.engineInited = (ret == SENoError);
if (self.engineInited) {
NSLog(@"初始化成功");
[self speechEngineInitSucceeded];
} else {
NSLog(@"初始化失败,返回值: %d", ret);
[self speechEngineInitFailed:ret];
}
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
}
#pragma mark - UI Actions
- (IBAction)switchEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self clearResult:nil];
self.startEngineButton.enabled = FALSE;
self.pauseResumeButton.enabled = FALSE;
if (self.engineInited) {
self.referTextView.editable = TRUE;
[self uninitEngine];
self.engineInited = FALSE;
[self.statusTextView setText:@"Waiting for init."];
self.engineSwitchButton.enabled = TRUE;
[self.engineSwitchButton setTitle:@"Init Engine" forState:UIControlStateNormal];
self.stopEngineButton.enabled = FALSE;
} else {
self.referTextView.editable = TRUE;
[self initEngine];
}
}
- (IBAction)startEngineBtnClicked:(id)sender {
NSLog(@"Start engine, current status: %d", self.engineStarted);
if (!self.engineStarted) {
[self clearResult:nil];
// DirectiveSYNC_STOP
NSLog(@"关闭引擎(同步)");
NSLog(@"Directive: SEDirectiveSyncStopEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
if (ret != SENoError) {
NSLog(@"Send directive syncstop failed: %d", ret);
} else {
NSLog(@"启动引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
NSString* startPayload = [NSString stringWithFormat:@"{\"req_params\":{\"text\":\"%@\",\"speaker\":\"zh_female_roumeinvyou_emo_v2_mars_bigtts\",\"audio_params\":{\"emotion\":\"excited\",\"loudness_rate\":50}}}", self.referTextView.text];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine data:startPayload];
if (SENoError != ret) {
NSString* message = [NSString stringWithFormat:@"发送启动引擎指令失败: %d", ret];
[self sendStartEngineDirectiveFailed:message];
return;
}
ret = [self.curEngine sendDirective:SEDirectiveEventStartSession data:@""];
if (ret != SENoError) {
NSLog(@"Start Session faile: %d", ret);
}
}
}
}
- (IBAction)stopEngineBtnClicked:(id)sender {
NSLog(@"关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)pausePlayback {
NSLog(@"暂停播放");
NSLog(@"Directive: SEDirectivePausePlayer");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectivePausePlayer];
if (ret == SENoError) {
self.playerPaused = TRUE;
[self.pauseResumeButton setTitle:@"Resume" forState:UIControlStateNormal];
}
NSLog(@"Pause playback status: %d", ret);
}
- (void)resumePlayback {
NSLog(@"继续播放");
NSLog(@"Directive: SEDirectiveResumePlayer");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveResumePlayer];
if (ret == SENoError) {
self.playerPaused = FALSE;
[self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
}
NSLog(@"Resume playback status: %d", ret);
}
- (IBAction)controlPlayingStatus:(id)sender {
NSLog(@"Pause or resume player, current player status: %hhd", self.playerPaused);
if (self.playerPaused) {
[self resumePlayback];
} else {
[self pausePlayback];
}
}
- (IBAction)clearResult:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:@""];
});
}
#pragma mark - Message Callback
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
NSLog(@"Callback: 引擎启动成功: data: %@", data);
[self speechEngineStarted];
break;
case SEEngineStop:
NSLog(@"Callback: 引擎关闭: data: %@", data);
[self speechEngineStopped];
break;
case SEEngineError:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEEventConnectionStarted:
NSLog(@"Callback: SEEventConnectionStarted");
break;
case SEEventConnectionFailed:
NSLog(@"Callback: SEEventConnectionFailed");
break;
case SEEventConnectionFinished:
NSLog(@"Callback: SEEventConnectionFinished");
break;
case SEEventSessionStarted:
NSLog(@"Callback: SEEventSessionStarted");
break;
case SEEventSessionCanceled:
NSLog(@"Callback: SEEventSessionCanceled");
break;
case SEEventSessionFinished:
NSLog(@"Callback: SEEventSessionFinished");
break;
case SEEventSessionFailed:
NSLog(@"Callback: SEEventSessionFailed");
break;
case SEEventTTSSentenceStart:
NSLog(@"Callback: 合成开始 SentenceStart: %@", data);
break;
case SEEventTTSSentenceEnd:
NSLog(@"Callback: 合成结束 SentenceEnd: %@", data);
case SEEventTTSResponse:
NSLog(@"Callback: 收到合成音频 TTSResponse: %@", data);
case SEEventTTSEnded:
NSLog(@"Callback: TTSEnd: %@", data);
case SEPlayerAudioData:
NSLog(@"Callback: 接收到播放的pcm音频");
break;
case SEPlayerStartPlayAudio:
NSLog(@"Callback: 开始播放TTS音频");
[self speechStartPlaying:nil];
break;
case SEPlayerFinishPlayAudio:
NSLog(@"Callback: 结束播放TTS音频");
[self speechFinishPlaying:nil];
break;
default:
break;
}
}
- (void)speechEngineInitSucceeded {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineSwitchButton.enabled = TRUE;
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@.", self.deviceID]];
self.referTextView.editable = TRUE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.engineSwitchButton.enabled = TRUE;
});
}
- (void)sendSynthesisDirectiveFailed:(NSString*)tipText {
NSLog(@"%@", tipText);
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:tipText];
[self.curEngine sendDirective:SEDirectiveStopEngine];
});
}
- (void)sendStartEngineDirectiveFailed:(NSString*)tipText {
NSLog(@"%@", tipText);
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:tipText];
self.engineStarted = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.referTextView.editable = TRUE;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
self.referTextView.editable = TRUE;
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
[self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
self.pauseResumeButton.enabled = FALSE;
self.playerPaused = FALSE;
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
id json_obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
if ([json_obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *error_info = json_obj;
[self setResultText:[NSString stringWithFormat:@"%@", error_info]];
}
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *currentText = self.resultTextView.text ?: @"";
NSString *newText = [NSString stringWithFormat:@"%@\n%@", currentText, result];
[self.resultTextView setText:newText];
if (self.resultTextView.text.length > 0) {
NSRange bottomRange = NSMakeRange(self.resultTextView.text.length - 1, 1);
[self.resultTextView scrollRangeToVisible:bottomRange];
[self.resultTextView layoutIfNeeded];
}
});
}
- (void)speechStartPlaying:(NSData *)data {
NSLog(@"TTS start playing");
dispatch_async(dispatch_get_main_queue(), ^{
self.pauseResumeButton.enabled = TRUE;
});
}
- (void)speechFinishPlaying :(NSData *)data {
NSLog(@"TTS finish playing");
[self setResultText:@"playing finished"];
dispatch_async(dispatch_get_main_queue(), ^{
//
[self stopEngineBtnClicked:nil];
});
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_UNITTS forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,18 @@
//
// VadViewController.h
// SpeechDemo
//
// Created by bytedance on 2023/1/30.
// Copyright © 2023 tinalei.richard. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface VadViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,455 @@
//
// VadViewController.m
// SpeechDemo
//
// Created by bytedance on 2023/1/30.
// Copyright © 2023 tianlei.richard. All rights reserved.
//
#import "VadViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface VadViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long talkingFinisheTimestamp;
@property (nonatomic, assign) long vadDuration;
@property (nonatomic, assign) long vadBeginPosition;
@property (nonatomic, assign) long vadEndPosition;
@property (strong, nonatomic) NSString *debugPath;
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation VadViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_VAD];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
UILongPressGestureRecognizer *longPgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPressTriggered:)];
longPgr.minimumPressDuration = 0.5;
[self.longPressButton addGestureRecognizer:longPgr];
self.streamRecorder = [ViewController getStreamRecorder];
self.engineStarted = FALSE;
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEVadSilence:
break;
case SEVadSil2Speech:
[self messageVadBegin:data];
break;
case SEVadSpeech:
[self messasgeVadSpeech:data];
break;
case SEVadSpeech2Sil:
[self messageVadEnd:data];
break;
case SEVadAudioData:
self.vadDuration += data.length;
break;
case SEEngineLog:
NSLog(@"engine log: %s", (char*)data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)initEngine:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示音频活性检测结果"];
}
- (IBAction)startEngine:(id)sender {
NSLog(@"Start engine.");
NSString* appID = [self.settings getString:SETTING_APPID];
[self.curEngine setStringParam:appID.length <= 0 ? SDEF_APPID : appID forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_RESTART_AUDIO_SESSION_ENABLE]
forKey:SE_PARAMS_KEY_RESTART_AUDIOSESSION_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE]
forKey:SE_PARAMS_KEY_RESUME_OTHERS_INTERRUPTED_PLAYBACK_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME] forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_HEAD_SILENCE_THRESHOLD] forKey:SE_PARAMS_KEY_VAD_HEAD_SILENCE_THRESHOLD_INT];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_TAIL_SILENCE_THRESHOLD] forKey:SE_PARAMS_KEY_VAD_TAIL_SILENCE_THRESHOLD_INT];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"vad_rec_file.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
- (IBAction)stopEngine:(id)sender {
NSLog(@"Stop engine.");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)longPressTriggered:(UILongPressGestureRecognizer *)longPgr {
if (longPgr.state == UIGestureRecognizerStateBegan) {
NSLog(@"Long press begin.");
[self setResultText:@""];
NSString* appID = [self.settings getString:SETTING_APPID];
[self.curEngine setStringParam:appID.length <= 0 ? SDEF_APPID : appID forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_RESTART_AUDIO_SESSION_ENABLE]
forKey:SE_PARAMS_KEY_RESTART_AUDIOSESSION_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE]
forKey:SE_PARAMS_KEY_RESUME_OTHERS_INTERRUPTED_PLAYBACK_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME]
forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_HEAD_SILENCE_THRESHOLD] forKey:SE_PARAMS_KEY_VAD_HEAD_SILENCE_THRESHOLD_INT];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_TAIL_SILENCE_THRESHOLD] forKey:SE_PARAMS_KEY_VAD_TAIL_SILENCE_THRESHOLD_INT];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"vad_rec_file.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
} else if (longPgr.state == UIGestureRecognizerStateEnded) {
NSLog(@"Long press ended.");
self.talkingFinisheTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
self.engineInitButton.enabled = FALSE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
[self initOfflineModel:^() {
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setStringParam:SDEF_APPID forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
[self.curEngine setIntParam:1 forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME]
forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
[self.curEngine setStringParam:@"" forKey:SE_PARAMS_KEY_VAD_REC_PATH_STRING];
if ([self.settings getBool:SETTING_VAD_RECORDER_SAVE]) {
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_VAD_REC_PATH_STRING];
}
[self.curEngine setIntParam:4000 forKey:SE_PARAMS_KEY_VAD_HEAD_SILENCE_THRESHOLD_INT];
[self.curEngine setIntParam:2000 forKey:SE_PARAMS_KEY_VAD_TAIL_SILENCE_THRESHOLD_INT];
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
[self.curEngine setStringParam:SE_VAD_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
[self.curEngine setIntParam:16000 forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
NSString* aedResourcePath = [[SpeechResourceManager shareInstance] getModelPath:SE_AED_MODEL];
NSLog(@"petrel aed resource path: %@", aedResourcePath);
[self.curEngine setStringParam:aedResourcePath forKey:SE_PARAMS_KEY_AED_RESOURCE_PATH_STRING];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000) {
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
}
}
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
}
if (ret == SENoError) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed:ret];
}
} fail:^{
[self speechEngineInitFailed:kSERDownloadFailed];
}];
}
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)initOfflineModel:(void(^)(void))succ fail:(void(^)(void))fail {
NSString *model = SE_AED_MODEL;
SpeechResourceManager *speechResourceManager = [SpeechResourceManager shareInstance];
[speechResourceManager checkModelVersion:model completion:^(SEResourceStatus status, BOOL needUpdate, NSData *data) {
NSLog(@"Need update: %@", needUpdate ? @"YES" : @"NO");
if (status != kSERSuccess || needUpdate == NO) {
if ([speechResourceManager checkModelExist:model]) {
NSLog(@"Model exist!");
succ();
} else {
NSLog(@"Model not exist!");
fail();
}
return;
}
// need to update model
[speechResourceManager fetchModelByName:model completion:^(SEResourceStatus status, NSData* data) {
NSLog(@"Completion: %@", status == kSERSuccess ? @"success" : @"fail");
if (status == kSERSuccess) {
succ();
} else {
NSLog(@"Failed: %d", status);
fail();
}
}];
}];
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = FALSE;
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
[self.streamRecorder setSpeechEngine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed:(int)initStatus {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
self.vadDuration = 0;
self.vadBeginPosition = 0;
self.vadEndPosition = 0;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
self.longPressButton.enabled = FALSE;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
self.longPressButton.enabled = TRUE;
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self stopEngine:nil];
});
}
- (void)messageVadBegin:(NSData *)data {
id json_obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
if ([json_obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *vad_info = json_obj;
self.vadBeginPosition = [[vad_info objectForKey:@"start"] doubleValue];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[NSString stringWithFormat:@"Vad begin, bos: %ld", self.vadBeginPosition]];
});
}
- (void)messasgeVadSpeech:(NSData *)data {
id json_obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
if ([json_obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *vad_info = json_obj;
double currentSegEnd = [[vad_info objectForKey:@"end"] doubleValue];
if (currentSegEnd > self.vadEndPosition) {
self.vadEndPosition = currentSegEnd;
}
}
}
- (void)messageVadEnd:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[NSString stringWithFormat:@"Vad end, eos: %ld, speech duration: %ldms.", self.vadEndPosition, self.vadDuration/(2*16000/1000)]];
[self stopEngine:nil];
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_VAD forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,14 @@
//
// VoiceCloneViewController.h
// SpeechDemo
//
// Created by bytedance on 2021/2/20.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface VoiceCloneViewController : UIViewController
@end

View File

@ -0,0 +1,649 @@
//
// VoiceCloneViewController.m
// SpeechDemo
//
// Created by bytedance on 2021/2/20.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import "VoiceCloneViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
@interface VoiceCloneViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *getTaskButton;
@property (weak, nonatomic) IBOutlet UIButton *checkEnvButton;
@property (weak, nonatomic) IBOutlet UIButton *voiceRecordButton;
@property (weak, nonatomic) IBOutlet UIButton *finishTalkingButton;
@property (weak, nonatomic) IBOutlet UIButton *getTrainStatusButton;
@property (weak, nonatomic) IBOutlet UIButton *submitTaskButton;
@property (weak, nonatomic) IBOutlet UIButton *delTrainDataButton;
@property (weak, nonatomic) IBOutlet UIButton *nextRecordTaskButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineInited;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long talkingFinisheTimestamp;
@property (nonatomic, assign) long startEngineTimestamp;
@property (strong, nonatomic) NSString *debugPath;
@property (weak, nonatomic) StreamRecorder *streamRecorder;
// voiceclone json param
@property (strong, nonatomic) NSString *voiceCloneCurText;
@property (assign, nonatomic) NSInteger voiceCloneCurTextSeq;
@property (assign, nonatomic) NSInteger voiceCloneCurTaskId;
// voiceclone last get-task-info result
@property (strong, nonatomic) NSMutableDictionary *voiceCloneTaskInfo;
// ViewStatus
typedef enum ViewStatus : NSUInteger {
BEFORE_INIT,
INITING,
BEFORE_GET_TASK,
BEFORE_CHECK_ENV,
BEFORE_RECORD,
RECORDING,
BEFORE_SUBMIT,
AFTER_SUBMIT,
} ViewStatus;
// prev ViewStatus
@property (assign, nonatomic) ViewStatus preViewStatus;
// current ViewStatus
@property (assign, nonatomic) ViewStatus curViewStatus;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation VoiceCloneViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_VOICECLONE];
self.voiceCloneTaskInfo = NULL;
[self updateParamJsonObj];
[self switchViewStatus:BEFORE_INIT];
[self decorateTextView:self.referTextView];
[self decorateTextView:self.resultTextView];
[self.referTextView setDelegate:self];
self.referTextView.editable = FALSE;
self.engineInited = FALSE;
self.engineStarted = FALSE;
self.streamRecorder = [ViewController getStreamRecorder];
[self.statusTextView setText:@"Waiting for init."];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appWillTerminate:)
name:UIApplicationWillTerminateNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)updateParamJsonObj {
if (self.voiceCloneTaskInfo != NULL) {
[[self.voiceCloneTaskInfo objectForKey:@"progress"]intValue];
} else {
[self updateParamJsonObj:0];
}
}
- (void)updateParamJsonObj:(int)text_seq {
if (self.voiceCloneTaskInfo == NULL) {
self.voiceCloneCurText = @"";
self.voiceCloneCurTextSeq = 0;
self.voiceCloneCurTaskId = [self.settings getInt:SETTING_VOICECLONE_TASKID];
} else {
NSArray *text_arr = [self.voiceCloneTaskInfo objectForKey:@"texts"];
self.voiceCloneCurText = [text_arr objectAtIndex:text_seq];
self.voiceCloneCurTextSeq = text_seq;
self.voiceCloneCurTaskId =[[self.voiceCloneTaskInfo objectForKey:@"task_id"]intValue];
}
// update refer text
[self.referTextView setText:[NSString stringWithFormat:@"Current task: %d\nCurrent task sequence: %d\nCurrent text: %@", (int)self.voiceCloneCurTaskId, (int)self.voiceCloneCurTextSeq, self.voiceCloneCurText]];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
- (void)switchViewStatus:(ViewStatus) status {
self.preViewStatus = self.curViewStatus;
self.curViewStatus = status;
switch (status) {
case BEFORE_INIT:
[self.engineSwitchButton setTitle:@"InitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = TRUE;
self.getTaskButton.enabled = FALSE;
self.checkEnvButton.enabled = FALSE;
self.voiceRecordButton.enabled = FALSE;
self.finishTalkingButton.enabled = FALSE;
self.getTrainStatusButton.enabled = FALSE;
self.submitTaskButton.enabled = FALSE;
self.delTrainDataButton.enabled = FALSE;
self.nextRecordTaskButton.enabled = FALSE;
break;
case INITING:
[self.engineSwitchButton setTitle:@"InitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = FALSE;
self.getTaskButton.enabled = FALSE;
self.checkEnvButton.enabled = FALSE;
self.voiceRecordButton.enabled = FALSE;
self.finishTalkingButton.enabled = FALSE;
self.getTrainStatusButton.enabled = FALSE;
self.submitTaskButton.enabled = FALSE;
self.delTrainDataButton.enabled = FALSE;
self.nextRecordTaskButton.enabled = FALSE;
break;
case RECORDING:
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = FALSE;
self.getTaskButton.enabled = FALSE;
self.checkEnvButton.enabled = FALSE;
self.voiceRecordButton.enabled = FALSE;
self.finishTalkingButton.enabled = TRUE;
self.getTrainStatusButton.enabled = FALSE;
self.submitTaskButton.enabled = FALSE;
self.delTrainDataButton.enabled = FALSE;
self.nextRecordTaskButton.enabled = FALSE;
break;
case BEFORE_GET_TASK:
case BEFORE_CHECK_ENV:
case BEFORE_RECORD:
case BEFORE_SUBMIT:
case AFTER_SUBMIT:
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = TRUE;
self.getTaskButton.enabled = TRUE;
self.checkEnvButton.enabled = TRUE;
self.voiceRecordButton.enabled = TRUE;
self.finishTalkingButton.enabled = FALSE;
self.getTrainStatusButton.enabled = TRUE;
self.submitTaskButton.enabled = TRUE;
self.delTrainDataButton.enabled = TRUE;
self.nextRecordTaskButton.enabled = TRUE;
break;
default:
break;
}
}
#pragma mark - Notifications
- (void)appDidEnterBackground:(UIApplication *)application; {
// if (self.engineStarted) {
// [self stopEngine:nil];
// }
}
-(void)appWillTerminate:(NSNotification*)note {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillTerminateNotification
object:nil];
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEVoiceCloneGetTaskResult:
[self speechVoiceCloneGetTaskResult:data];
break;
case SEVoiceCloneCheckEnvResult:
[self speechVoiceCloneCheckEnvResult:data];
break;
case SEVoiceCloneRecordVoiceResult:
[self speechVoiceCloneRecordVoiceResult:data];
break;
case SEVoiceCloneQueryStatusResult:
[self speechVoiceCloneQueryStatusResult:data];
break;
case SEVoiceCloneSubmitTaskResult:
[self speechVoiceCloneSubmitTaskResult:data];
break;
case SEVoiceCloneDeleteDataResult:
[self speechVoiceCloneDeleteDataResult:data];
break;
case SEEngineLog:
NSLog(@"engine log: %s", data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)switchEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self.referTextView setText:@""];
self.voiceCloneTaskInfo = NULL;
[self updateParamJsonObj];
if (self.engineInited) {
[self uninitEngine];
self.engineInited = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self switchViewStatus:BEFORE_INIT];
} else {
[self switchViewStatus:INITING];
[self initEngine];
}
}
- (IBAction)stopEngine:(id)sender {
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (IBAction)getTask:(id)sender {
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_UID] forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_VOICECLONE_GENDER] forKey:SE_PARAMS_KEY_VOICECLONE_GENDER_BOOL];
[self.curEngine sendDirective:SEDirectiveVoiceCloneGetTask];
}
- (IBAction)checkEnv:(id)sender {
if (![self checkRecorder]) {
return;
}
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_UID] forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:self.voiceCloneCurTaskId forKey:SE_PARAMS_KEY_VOICECLONE_TASKID_INT];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveVoiceCloneCheckEnv];
if (ret != SENoError) {
[self.resultTextView setText:[NSString stringWithFormat:@"checkEnv failed: %d", ret]];
} else {
[self switchViewStatus:RECORDING];
}
}
- (IBAction)voiceRecord:(id)sender {
if (![self checkRecorder]) {
return;
}
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_UID] forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:self.voiceCloneCurTaskId forKey:SE_PARAMS_KEY_VOICECLONE_TASKID_INT];
[self.curEngine setStringParam:self.voiceCloneCurText forKey:SE_PARAMS_KEY_VOICECLONE_TEXT_STRING];
[self.curEngine setIntParam:self.voiceCloneCurTextSeq forKey:SE_PARAMS_KEY_VOICECLONE_TEXT_SEQ_INT];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveVoiceCloneRecordVoice];
if (ret != SENoError) {
[self.resultTextView setText:[NSString stringWithFormat:@"voiceRecord failed: %d", ret]];
} else {
[self switchViewStatus:RECORDING];
}
}
- (IBAction)finishTalking:(id)sender {
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
- (IBAction)getTrainStatus:(id)sender {
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_UID] forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_QUERY_UIDS] forKey:SE_PARAMS_KEY_VOICECLONE_QUERY_UIDS_STRING];
[self.curEngine sendDirective:SEDirectiveVoiceCloneQueryStatus];
}
- (IBAction)submitTask:(id)sender {
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_UID] forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:self.voiceCloneCurTaskId forKey:SE_PARAMS_KEY_VOICECLONE_TASKID_INT];
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_VOICE_TYPE] forKey:SE_PARAMS_KEY_VOICECLONE_VOICE_TYPE_STRING];
[self.curEngine sendDirective:SEDirectiveVoiceCloneSubmitTask];
}
- (IBAction)delTrainData:(id)sender {
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_UID] forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:self.voiceCloneCurTaskId forKey:SE_PARAMS_KEY_VOICECLONE_TASKID_INT];
[self.curEngine sendDirective:SEDirectiveVoiceCloneDeleteData];
}
- (IBAction)goToNextTaskInfo:(id)sender {
if (self.voiceCloneTaskInfo != NULL) {
NSArray *text_arr = [self.voiceCloneTaskInfo objectForKey:@"texts"];
if (text_arr.count != 0) {
[self updateParamJsonObj:((int)self.voiceCloneCurTextSeq + 1) % text_arr.count];
[self.resultTextView setText:@""];
return;
}
}
[self.resultTextView setText:@"You should get task info sucessfully first!"];
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_TOKEN] forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setIntParam:1 forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_ADDRESS] forKey:SE_PARAMS_KEY_VOICECLONE_ADDRESS_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICECLONE_STREAM_ADDRESS] forKey:SE_PARAMS_KEY_VOICECLONE_STREAM_ADDRESS_STRING];
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_VOICECLONE_ENABLE_DUMP_BOOL];
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_VOICECLONE_REC_PATH_STRING];
[self.curEngine setStringParam:SE_VOICECLONE_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
[self.curEngine setIntParam:[self.settings getInt:SETTING_SAMPLE_RATE] forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != [self.settings getInt:SETTING_SAMPLE_RATE]) {
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
}
}
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
}
self.engineInited = (ret == SENoError);
if (self.engineInited) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed];
}
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
}
- (bool)checkRecorder {
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return false;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"voiceclone_rec_file.pcm"];
NSLog(@"test file path: %@", file_path);
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
return true;
}
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
});
}
- (void)speechEngineInitOk {
[self.streamRecorder setSpeechEngine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self switchViewStatus:BEFORE_GET_TASK];
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self switchViewStatus:BEFORE_INIT];
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.startEngineTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
if (self.startEngineTimestamp > 0) {
long response_delay = [self timeDelayFrom:self.startEngineTimestamp];
self.startEngineTimestamp = 0;
[self.resultTextView setText:[NSString stringWithFormat:@"%@\nVoiceClone cost: %ld\n", self.resultTextView.text, response_delay]];
}
});
}
- (void)
speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self stopEngine:nil];
if (self.curViewStatus == RECORDING) {
[self switchViewStatus:self.preViewStatus];
}
});
}
- (void)
speechEngineResult:(NSString *)text {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:text];
});
}
- (void)speechVoiceCloneGetTaskResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
NSArray *task_arr = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (task_arr.count <= 0) {
[self.resultTextView setText:@"No task data!"];
return;
}
[self.resultTextView setText:@""];
self.voiceCloneCurTaskId = [self.settings getInt:SETTING_VOICECLONE_TASKID];
for (int i = 0; i < task_arr.count; ++i) {
NSDictionary *task_info = [task_arr objectAtIndex:i];
int task_id = [[task_info objectForKey:@"task_id"] intValue];
if ((int)self.voiceCloneCurTaskId == task_id) {
self.voiceCloneTaskInfo = [NSMutableDictionary dictionaryWithDictionary:task_info];
break;
}
}
// no match task_id
if (self.voiceCloneTaskInfo == NULL) {
self.voiceCloneTaskInfo = [task_arr objectAtIndex:0];
[self.resultTextView setText:[NSString stringWithFormat:@"Find task %d failed! Choose task: %d", (int)self.voiceCloneCurTaskId, [[self.voiceCloneTaskInfo objectForKey:@"task_id"] intValue]]];
}
// update task progress
int prog = [[self.voiceCloneTaskInfo objectForKey:@"progress"] intValue];
NSArray *texts_arr = [self.voiceCloneTaskInfo objectForKey:@"texts"];
[self updateParamJsonObj:(prog % texts_arr.count)];
if (prog < texts_arr.count && prog >= 0) {
[self.resultTextView setText:[NSString stringWithFormat:@"%@\nGet task info success!\nRecord task unfinished.\nGo to check environment", self.resultTextView.text]];
[self switchViewStatus:BEFORE_CHECK_ENV];
} else {
[self.resultTextView setText:[NSString stringWithFormat:@"%@\nGet task info success!\nRecord task finished!\nGo to submit task or query train status.", self.resultTextView.text]];
[self switchViewStatus:AFTER_SUBMIT];
}
});
}
- (void)speechVoiceCloneCheckEnvResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
NSDictionary *env_info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
int status = [[env_info objectForKey:@"status"] intValue];
if (status > 0) {
[self.resultTextView setText:[NSString stringWithFormat:@"Checking...\nCurrent status: %d\nCurrent noise: %lf", status, [[env_info objectForKey:@"noise"] doubleValue]]];
} else if (status < 0) {
[self.resultTextView setText:[NSString stringWithFormat:@"Check failed! status: %d", status]];
[self switchViewStatus:BEFORE_CHECK_ENV];
} else {
[self.resultTextView setText:[NSString stringWithFormat:@"Check success! status: %d", status]];
[self switchViewStatus:BEFORE_RECORD];
}
});
}
- (void)speechVoiceCloneRecordVoiceResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
NSDictionary *record_res = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
int status = [[record_res objectForKey:@"status"] intValue];
if (status > 0) {
} else if (status < 0) {
[self.resultTextView setText:[NSString stringWithFormat:@"Check failed! status: %d, wrong_index: %@", status, [record_res objectForKey:@"wrong_index"]]];
[self switchViewStatus:BEFORE_RECORD];
} else {
[self.resultTextView setText:[NSString stringWithFormat:@"Check success! status: %d", status]];
NSArray* text_arr = [self.voiceCloneTaskInfo objectForKey:@"texts"];
if (text_arr.count > 0 && self.voiceCloneCurTextSeq + 1 < text_arr.count) {
[self.resultTextView setText:[NSString stringWithFormat:@"Text %d success! status: %d", (int)self.voiceCloneCurTextSeq, status]];
[self switchViewStatus:BEFORE_RECORD];
} else {
[self.resultTextView setText:[NSString stringWithFormat:@"Voice record task: %d finished!\nGo to submit task!", (int)self.voiceCloneCurTaskId]];
[self switchViewStatus:BEFORE_SUBMIT];
}
}
});
}
- (void)speechVoiceCloneQueryStatusResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
NSArray *train_status_arr = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
[self.resultTextView setText:@""];
for (int i = 0; i < train_status_arr.count; ++i) {
NSDictionary *item = [train_status_arr objectAtIndex:i];
NSString *uid = [item objectForKey:@"uid"];
int status = [[item objectForKey:@"status"] intValue];
int task_id = [[item objectForKey:@"task_id"] intValue];
NSString *extra = [item objectForKey:@"extra"];
[self.resultTextView setText:[NSString stringWithFormat:@"%@%@: status: %d; task_id: %d; extra: %@\n\n", self.resultTextView.text, uid, status, task_id, extra]];
}
});
}
- (void)speechVoiceCloneSubmitTaskResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSString *voiceType = [result objectForKey:@"voice_type"];
[self.resultTextView setText:[NSString stringWithFormat:@"Task submit success, voice type: %@", voiceType]];
[self switchViewStatus:AFTER_SUBMIT];
});
}
- (void)speechVoiceCloneDeleteDataResult:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:@"Task data del success"];
});
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_VOICECLONE forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,15 @@
//
// VoiceConvViewController.h
// SpeechDemo
//
// Created by chengzihao.ds on 2021/4/1.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface VoiceConvViewController : UIViewController
@end

View File

@ -0,0 +1,432 @@
//
// VoiceConvViewController.m
// SpeechDemo
//
// Created by chengzihao.ds on 2021/4/1.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "VoiceConvViewController.h"
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface VoiceConvViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *finishTalkingButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL signalEnabled;
@property (assign, nonatomic) BOOL engineInited;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long talkingFinisheTimestamp;
@property (nonatomic, assign) long startEngineTimestamp;
@property (strong, nonatomic) NSString *debugPath;
// ViewStatus
typedef enum ViewStatus : NSUInteger {
BEFORE_INIT,
INITING,
BEFORE_START,
RECORDING,
WAITING_RESULT,
} ViewStatus;
// Recorder
@property (weak, nonatomic) StreamRecorder *streamRecorder;
@property (strong, nonatomic) NSString *recFilePath;
@property (nonatomic, assign) long curResLength;
@property (nonatomic, assign) long recFileLength;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation VoiceConvViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_VOICECONV];
[self switchViewStatus:BEFORE_INIT];
[self decorateTextView:self.referTextView];
[self decorateTextView:self.resultTextView];
[self.referTextView setDelegate:self];
self.referTextView.editable = FALSE;
self.engineInited = FALSE;
self.engineStarted = FALSE;
self.streamRecorder = [ViewController getStreamRecorder];
[self.statusTextView setText:@"Waiting for init."];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appWillTerminate:)
name:UIApplicationWillTerminateNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
- (void)switchViewStatus:(ViewStatus) status {
switch (status) {
case BEFORE_INIT:
[self.engineSwitchButton setTitle:@"InitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = TRUE;
self.startEngineButton.enabled = FALSE;
self.finishTalkingButton.enabled = FALSE;
break;
case INITING:
[self.engineSwitchButton setTitle:@"InitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.finishTalkingButton.enabled = FALSE;
break;
case BEFORE_START:
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = TRUE;
self.startEngineButton.enabled = TRUE;
self.finishTalkingButton.enabled = FALSE;
break;
case RECORDING:
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = TRUE;
self.startEngineButton.enabled = FALSE;
self.finishTalkingButton.enabled = TRUE;
break;
case WAITING_RESULT:
[self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
self.engineSwitchButton.enabled = TRUE;
self.startEngineButton.enabled = FALSE;
self.finishTalkingButton.enabled = FALSE;
break;
default:
break;
}
}
#pragma mark - Notifications
- (void)appDidEnterBackground:(UIApplication *)application; {
// if (self.engineStarted) {
// [self stopEngine:nil];
// }
}
-(void)appWillTerminate:(NSNotification*)note {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillTerminateNotification
object:nil];
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped:data];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEVoiceConvResultAudio:
[self speechResultAudio:data];
break;
case SEEngineLog:
NSLog(@"engine log: %s", data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)switchEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
if (self.engineInited) {
[self switchViewStatus:BEFORE_INIT];
[self uninitEngine];
self.engineInited = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self.engineSwitchButton setTitle:@"InitEngine" forState:UIControlStateNormal];
} else {
[self switchViewStatus:INITING];
[self initEngine];
}
}
- (IBAction)startEngine:(id)sender {
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
return;
}
} else if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE]) {
// get file length
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:self.recFilePath]){
self.recFileLength = [[manager attributesOfItemAtPath:self.recFilePath error:nil] fileSize];
}
NSLog(@"Open record file: %@, length: %ld", self.recFilePath, self.recFileLength);
self.curResLength = 0;
[self.curEngine setStringParam:self.recFilePath forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICE] forKey:SE_PARAMS_KEY_VOICECONV_VOICE_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_VOICE_TYPE] forKey:SE_PARAMS_KEY_VOICECONV_VOICE_TYPE_STRING];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VOICECONV_RESULT_SAMPLE_RATE] forKey:SE_PARAMS_KEY_VOICECONV_RESULT_SAMPLE_RATE_INT];
[self.curEngine setIntParam:[self.settings getInt:SETTING_VOICECONV_REQUEST_INTERVAL] forKey:SE_PARAMS_KEY_VOICECONV_REQUEST_INTERVAL_INT];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SENoError) {
[self switchViewStatus:RECORDING];
} else {
[self.resultTextView setText:[NSString stringWithFormat:@"Start engine failed! ret: %d", ret]];
}
}
- (IBAction)stopEngine:(id)sender {
[self.curEngine sendDirective:SEDirectiveStopEngine];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
}
- (IBAction)finishTalking:(id)sender {
[self.curEngine sendDirective:SEDirectiveFinishTalking];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
[self switchViewStatus:WAITING_RESULT];
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
self.recFilePath = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"voiceconv_rec_file.pcm"];
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
NSLog(@"Debug path: %@", self.debugPath);
// recorder
SettingOptions *options = [self.settings getOptions:SETTING_RECORD_TYPE];
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setStringParam:@"test" forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setIntParam:1 forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
[self.curEngine setIntParam:16000 forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
[self.curEngine setStringParam:[self getRecorderType] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
[self.curEngine setStringParam:SE_VOICECONV_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
[self.curEngine setStringParam:@"wss://speech-test.bytedance.com" forKey:SE_PARAMS_KEY_VOICECONV_ADDRESS_STRING];
[self.curEngine setStringParam:@"/api/v1/voice_conv/ws" forKey:SE_PARAMS_KEY_VOICECONV_URI_STRING];
[self.curEngine setStringParam:@"default" forKey:SE_PARAMS_KEY_VOICECONV_CLUSTER_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_VOICECONV_ENABLE_RECORD_DUMP] forKey:SE_PARAMS_KEY_VOICECONV_ENABLE_RECORD_DUMP_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_VOICECONV_ENABLE_RESULT_DUMP] forKey:SE_PARAMS_KEY_VOICECONV_ENABLE_RESULT_DUMP_BOOL];
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_VOICECONV_AUDIO_PATH_STRING];
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
if ([self.streamRecorder getSampleRate] != 16000) {
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
}
}
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
}
self.engineInited = (ret == SENoError);
if (self.engineInited) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed];
}
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
}
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
});
}
- (void)speechEngineInitOk {
[self.streamRecorder setSpeechEngine:self.curEngine];
dispatch_async(dispatch_get_main_queue(), ^{
[self switchViewStatus:BEFORE_START];
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self switchViewStatus:BEFORE_INIT];
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.startEngineTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
});
}
- (void)speechEngineStopped:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_STREAM]) {
[self.streamRecorder stop];
}
// record result file
NSString* reqId = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSString* resultFile = [NSString stringWithFormat:@"voiceconv_%@.wav", reqId];
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
if (self.startEngineTimestamp > 0) {
long response_delay = [self timeDelayFrom:self.startEngineTimestamp];
self.startEngineTimestamp = 0;
[self.resultTextView setText:[NSString stringWithFormat:@"%@\nVoiceConv cost: %ld\nResult File: %@", self.resultTextView.text, response_delay, resultFile]];
}
[self switchViewStatus:BEFORE_START];
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self stopEngine:nil];
});
}
- (void)speechResultAudio:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[NSString stringWithFormat:@"Get audio data, size: %ld", data.length]];
// calculate progress
if ([[self getRecorderType] isEqualToString:SE_RECORDER_TYPE_FILE] && self.recFileLength != 0) {
self.curResLength += data.length;
long inputSampleRate = 16000;
long outputSampleRate = [self.settings getInt:SETTING_VOICECONV_RESULT_SAMPLE_RATE];
double progress = (double) self.curResLength / self.recFileLength * inputSampleRate / outputSampleRate;
[self.resultTextView setText:[NSString stringWithFormat:@"%@\nCurrent result length: %ld, total file length: %ld, progress: %lf", self.resultTextView.text, self.curResLength, self.recFileLength, progress]];
}
});
}
#pragma mark - Helper
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_VOICECONV forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,17 @@
//
// AppDelegate.h
// SpeechDemo
//
// Created by fangweiwei on 2019/12/6.
// Copyright © 2019 fangweiwei. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SpeechEngineToB/SpeechEngineToB-umbrella.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property(nonatomic, strong) UIWindow *window;
@property(nonatomic, readonly, strong) NSString *deviceID;
@end

View File

@ -0,0 +1,64 @@
//
// AppDelegate.m
// SpeechDemo
//
// Created by fangweiwei on 2019/12/6.
// Copyright © 2019 fangweiwei. All rights reserved.
//
#import "AppDelegate.h"
#import "SensitiveDefines.h"
@interface AppDelegate ()
@property(nonatomic, readwrite, strong) NSString *deviceID;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIDevice *device = [UIDevice currentDevice];
self.deviceID = [[device identifierForVendor] UUIDString];
//
BOOL status = [SpeechEngine prepareEnvironment];
if (status) {
[self setupResourceManager];
}
return status;
}
- (void) setupResourceManager {
NSLog(@"初始化模型资源管理器");
SpeechResourceManager *speechResourceManager = [SpeechResourceManager shareInstance];
[speechResourceManager setAppId:SDEF_APPID];
[speechResourceManager setAppVersion:@"1.0.0"];
[speechResourceManager setDeviceId:self.deviceID];
[speechResourceManager setRootPath: [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"models"]];
[speechResourceManager setOnlineModelEnable:YES];
[speechResourceManager setup];
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)){
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
@end

View File

@ -0,0 +1,16 @@
//
// SceneDelegate.h
// SpeechDemo
//
// Created by fangweiwei on 2019/12/6.
// Copyright © 2019 fangweiwei. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
@end

View File

@ -0,0 +1,50 @@
#import "SceneDelegate.h"
@interface SceneDelegate ()
@end
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions API_AVAILABLE(ios(13.0)){
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
}
- (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
- (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
- (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
- (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0)){
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
@end

View File

@ -0,0 +1,67 @@
//
// SettingViewDelegate.h
// SpeechDemo
//
// Created by bytedance on 2021/3/26.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Settings.h"
#pragma mark - SettingItemCell
@interface SettingItemCell : UITableViewCell<UITextFieldDelegate>
@property (weak, nonatomic) Settings *settings;
@property (weak, nonatomic) UITableView* parent;
-(void)init:(Settings*)settings parent:(UITableView*)parent;
-(SettingItem*)getCorrespondItem;
- (BOOL)textFieldShouldReturn:(UITextField *)textField;
@end
#pragma mark - SettingBoolCell
@interface SettingBoolCell : SettingItemCell
@property (weak, nonatomic) IBOutlet UILabel *keyLabel;
@property (weak, nonatomic) IBOutlet UISwitch *valSwitch;
-(void)switchAction:(UISwitch*)sender;
@end
#pragma mark - SettingIntCell
@interface SettingNumberCell : SettingItemCell
@property (weak, nonatomic) IBOutlet UILabel *keyLabel;
@property (weak, nonatomic) IBOutlet UITextField *valTextField;
-(void)textFieldDidChange :(UITextField *)textField;
@end
#pragma mark - SettingStringCell
@interface SettingStringCell : SettingItemCell
@property (weak, nonatomic) IBOutlet UILabel *keyLabel;
@property (weak, nonatomic) IBOutlet UITextField *valTextField;
-(void)textFieldDidChange :(UITextField *)textField;
@end
#pragma mark - SettingOptionsCell
@interface SettingOptionsCell : SettingItemCell<UIPickerViewDelegate, UIPickerViewDataSource>
@property (weak, nonatomic) IBOutlet UILabel *keyLabel;
@property (weak, nonatomic) IBOutlet UITextField *valTextField;
@property (strong, nonatomic) UIPickerView *pickerView;
@property (strong, nonatomic) UIToolbar *pickerToolbar;
-(void)pickerConfirm;
-(void)pickerCancel;
@end
#pragma mark - SettingViewDelegate
@interface SettingViewDelegate : NSObject<UITableViewDelegate, UITableViewDataSource>
@property (strong, nonatomic) Settings* settings;
+ (instancetype)build:(Settings*)settings;
@end

View File

@ -0,0 +1,280 @@
//
// SettingViewDelegate.m
// SpeechDemo
//
// Created by bytedance on 2021/3/26.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "SettingViewDelegate.h"
#pragma mark - SettingItemCell
@implementation SettingItemCell
-(void)init:(Settings*)settings parent:(UITableView*)parent {
self.settings = settings;
self.parent = parent;
}
-(SettingItem*)getCorrespondItem {
NSIndexPath* path = [self.parent indexPathForCell:self];
NSMutableArray* group = self.settings.configs[path.section];
return group[path.row];
}
// auto close keyboard when click return
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
@end
#pragma mark - SettingBoolCell
@implementation SettingBoolCell
-(void)init:(Settings*)settings parent:(UITableView*)parent {
[super init:settings parent:parent];
[self.valSwitch addTarget:self action:@selector(switchAction:) forControlEvents:UIControlEventValueChanged];
}
-(void)switchAction:(UISwitch*)sender {
SettingItem* item = [self getCorrespondItem];
[self.settings setBool:item.key val:sender.isOn];
}
@end
#pragma mark - SettingNumberCell
@implementation SettingNumberCell
-(void)init:(Settings*)settings parent:(UITableView*)parent {
[super init:settings parent:parent];
self.valTextField.delegate = self;
[self.valTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
-(void)textFieldDidChange :(UITextField *)textField {
SettingItem* item = [self getCorrespondItem];
// if input empty, do nothing
if ([textField.text length] > 0) {
[self.settings setDouble:item.key val:[textField.text doubleValue]];
}
}
@end
#pragma mark - SettingStringCell
@implementation SettingStringCell
-(void)init:(Settings*)settings parent:(UITableView*)parent {
[super init:settings parent:parent];
self.valTextField.delegate = self;
[self.valTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
-(void)textFieldDidChange :(UITextField *)textField {
SettingItem* item = [self getCorrespondItem];
[self.settings setString:item.key val:textField.text];
}
@end
#pragma mark - SettingOptionsCell
@implementation SettingOptionsCell
-(void)init:(Settings*)settings parent:(UITableView*)parent {
[super init:settings parent:parent];
// init picker view
self.pickerView = [[UIPickerView alloc] init];
self.pickerView.dataSource = self;
self.pickerView.delegate = self;
// init picker toolbar
self.pickerToolbar = [[UIToolbar alloc]initWithFrame:
CGRectMake(0, self.frame.size.height-
self.pickerView.frame.size.height-20, self.bounds.size.width, 40)];
UIBarButtonItem *confirmBtn = [[UIBarButtonItem alloc] initWithTitle:@"Confirm" style:UIBarButtonItemStyleDone target:self action:@selector(pickerConfirm)];
UIBarButtonItem *flexibleBBI = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
[self.pickerToolbar setBarStyle:UIBarStyleDefault];
NSArray *toolbarItems = [NSArray arrayWithObjects:flexibleBBI, confirmBtn, nil];
[self.pickerToolbar setItems:toolbarItems];
// show pickerView
self.valTextField.delegate = self;
self.valTextField.inputView = self.pickerView;
self.valTextField.inputAccessoryView = self.pickerToolbar;
}
// PickerView width
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
return self.bounds.size.width;
}
// PickerView select event
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
SettingItem* item = [self getCorrespondItem];
SettingOptions* options = (SettingOptions*)item.value;
options.chooseIdx = (int)row;
[self.settings setOptions:item.key val:options];
[self.valTextField setText:options.optionsArray[row]];
}
// PickerView show options
-(NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
SettingItem* item = [self getCorrespondItem];
SettingOptions* options = (SettingOptions*)item.value;
return options.optionsArray[row];
}
// PickerView column number
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
// PickerView row number for each column
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
SettingItem* item = [self getCorrespondItem];
return ((SettingOptions*)item.value).optionsArray.count;
}
// PickerToolbar confirm button click event
-(void)pickerConfirm {
if ([self.valTextField isFirstResponder]) {
[self.valTextField resignFirstResponder];
}
}
@end
#pragma mark - SettingViewDelegate
@implementation SettingViewDelegate
static NSString* nibName = @"SpeechSettingItem";
static CGFloat sectionHeaderHeight = 30;
static CGFloat sectionFooterHeight = 30;
+ (instancetype)build:(Settings*)settings {
SettingViewDelegate* instance = [[self alloc] init];
instance.settings = settings;
return instance;
}
// row number for each section
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSMutableArray *curGroup = _settings.configs[section];
NSLog(@"numberOfRowsInSection %ld", curGroup.count);
return curGroup.count;
}
// section number
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(@"numberOfSectionsInTableView %ld", _settings.groups.count);
return _settings.groups.count;
}
// section title
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
SettingItem *groupItem = _settings.groups[section];
return groupItem.key;
}
// section header height
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return sectionHeaderHeight;
}
// section footer height
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return sectionFooterHeight;
}
// generate TableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell;
NSMutableArray* group = _settings.configs[indexPath.section];
SettingItem* item = group[indexPath.row];
NSString *CellTableIndentifier = NULL;
switch (item.type) {
case kSettingBool:
{
CellTableIndentifier = @"SettingBool";
SettingBoolCell* bCell = [tableView dequeueReusableCellWithIdentifier:CellTableIndentifier];
if(bCell == nil) {
NSArray * nib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
bCell = [nib objectAtIndex:item.type];
[bCell init:_settings parent:tableView];
}
[bCell.keyLabel setText:item.key];
[bCell.valSwitch setOn:[(NSNumber*)item.value intValue] == 1];
cell = bCell;
}
break;
case kSettingNumber:
{
CellTableIndentifier = @"SettingNumber";
SettingNumberCell *iCell = [tableView dequeueReusableCellWithIdentifier:CellTableIndentifier];
if(iCell == nil) {
NSArray * nib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
iCell = [nib objectAtIndex:item.type];
[iCell init:_settings parent:tableView];
}
[iCell.keyLabel setText:item.key];
[iCell.valTextField setText:[(NSNumber*)item.value stringValue]];
[iCell.valTextField setPlaceholder:(NSString*)item.hint];
cell = iCell;
}
break;
case kSettingString:
{
CellTableIndentifier = @"SettingString";
SettingStringCell *sCell = [tableView dequeueReusableCellWithIdentifier:CellTableIndentifier];
if(sCell == nil) {
NSArray * nib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
sCell = [nib objectAtIndex:item.type];
[sCell init:_settings parent:tableView];
}
[sCell.keyLabel setText:item.key];
[sCell.valTextField setText:(NSString*)item.value];
[sCell.valTextField setPlaceholder:(NSString*)item.hint];
cell = sCell;
}
break;
case kSettingOptions:
{
CellTableIndentifier = @"SettingOptions";
SettingOptionsCell *oCell = [tableView dequeueReusableCellWithIdentifier:CellTableIndentifier];
if(oCell == nil) {
NSArray * nib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
oCell = [nib objectAtIndex:item.type];
[oCell init:_settings parent:tableView];
}
SettingOptions *options = (SettingOptions*)item.value;
[oCell.keyLabel setText:item.key];
[oCell.valTextField setText:options.optionsArray[options.chooseIdx]];
[oCell.valTextField setPlaceholder:item.hint];
cell = oCell;
}
break;
default:
NSLog(@"Unsupported SettingType: %ld", item.type);
break;
}
cell.accessibilityIdentifier = item.key;
return cell;
}
@synthesize description;
@synthesize hash;
@synthesize superclass;
@end

View File

@ -0,0 +1,83 @@
//
// Settings.h
// SpeechDemo
//
// Created by bytedance on 2021/3/26.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - SettingType
typedef NS_ENUM(NSInteger, SettingType) {
kSettingGroup = 0,
kSettingBool = 1,
kSettingNumber = 2,
kSettingString = 3,
kSettingOptions = 4,
};
#pragma mark - SettingOptions
@interface SettingOptions : NSObject
@property (strong, nonatomic) NSArray* optionsArray;
@property (assign, nonatomic) int chooseIdx;
+ (instancetype)build:(NSArray*)ops choose:(int)idx;
@end
#pragma mark - SettingItem
@interface SettingItem : NSObject
@property (assign, nonatomic) SettingType type;
@property (strong, nonatomic) NSString* key;
@property (strong, nonatomic) NSObject* value;
@property (strong, nonatomic) NSString* hint;
+ (instancetype)build:(SettingType)type key:(NSString*)key val:(NSObject*)val hint:(NSString*)hint;
+ (instancetype)buildGroup:(NSString*)key val:(NSString*)val hint:(NSString*)hint;
+ (instancetype)buildBool:(NSString*)key val:(BOOL)val hint:(NSString*)hint;
+ (instancetype)buildInt:(NSString *)key val:(int)val hint:(NSString *)hint;
+ (instancetype)buildDouble:(NSString *)key val:(double)val hint:(NSString *)hint;
+ (instancetype)buildString:(NSString*)key val:(NSString*)val hint:(NSString*)hint;
+ (instancetype)buildOptions:(NSString*)key val:(SettingOptions*)val hint:(NSString*)hint;
@end
#pragma mark - Settings
@interface Settings : NSObject
// kSettingGroup type, use SettingItem 1 dimension array.
@property (strong, nonatomic) NSMutableArray* groups;
// other type, use SettingItem 2 dimension array. 1st dimension for each group, 2nd dimension for detail SettingItem in group.
@property (strong, nonatomic) NSMutableArray* configs;
+ (instancetype)build;
- (void)registerItems:(NSArray*)cfgs;
- (void)setBool:(NSString*)key;
- (void)setBool:(NSString*)key val:(BOOL)val;
- (void)setInt:(NSString*)key;
- (void)setInt:(NSString*)key val:(int)val;
- (void)setDouble:(NSString*)key;
- (void)setDouble:(NSString*)key val:(double)val;
- (void)setString:(NSString*)key;
- (void)setString:(NSString*)key val:(NSString*)val;
- (void)setOptions:(NSString*)key;
- (void)setOptions:(NSString*)key val:(SettingOptions*)val;
- (BOOL)getBool:(NSString*)key;
- (BOOL)getBool:(NSString*)key def:(BOOL)def;
- (int)getInt:(NSString*)key;
- (int)getInt:(NSString*)key def:(int)def;
- (double)getDouble:(NSString*)key;
- (double)getDouble:(NSString*)key def:(double)def;
- (NSString*)getString:(NSString*)key;
- (NSString*)getString:(NSString*)key def:(NSString*)def;
- (SettingOptions*)getOptions:(NSString*)key;
- (SettingOptions*)getOptions:(NSString*)key def:(SettingOptions*)def;
- (NSString*)getOptionsValue:(NSString*)key;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,260 @@
//
// Settings.m
// SpeechDemo
//
// Created by bytedance on 2021/3/26.
// Copyright © 2021 chengzihao.ds. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Settings.h"
#pragma mark - SettingOptions
@implementation SettingOptions
+ (instancetype)build:(NSArray*)ops choose:(int)idx {
SettingOptions* instance = [[self alloc] init];
instance.optionsArray = ops;
instance.chooseIdx = idx;
return instance;
}
@end
#pragma mark - SettingItem
@implementation SettingItem
+ (instancetype)build:(SettingType)type key:(NSString*)key val:(NSObject*)val hint:(NSString*)hint {
SettingItem* instance = [[self alloc] init];
instance.key = key;
instance.type = type;
instance.value = val;
instance.hint = hint;
return instance;
}
+ (instancetype)buildGroup:(NSString*)key val:(NSString*)val hint:(NSString*)hint {
return [SettingItem build:kSettingGroup key:key val:val hint:hint];
}
+ (instancetype)buildBool:(NSString*)key val:(BOOL)val hint:(NSString*)hint {
return [SettingItem build:kSettingBool key:key val:val ? @1 : @0 hint:hint];
}
+ (instancetype)buildInt:(NSString*)key val:(int)val hint:(NSString*)hint {
return [SettingItem build:kSettingNumber key:key val:[NSNumber numberWithInt:val] hint:hint];
}
+ (instancetype)buildDouble:(NSString*)key val:(double)val hint:(NSString*)hint {
return [SettingItem build:kSettingNumber key:key val:[NSNumber numberWithDouble:val] hint:hint];
}
+ (instancetype)buildString:(NSString*)key val:(NSString*)val hint:(NSString*)hint {
return [SettingItem build:kSettingString key:key val:val hint:hint];
}
+ (instancetype)buildOptions:(NSString*)key val:(SettingOptions*)val hint:(NSString*)hint {
return [SettingItem build:kSettingOptions key:key val:val hint:hint];
}
@end
#pragma mark - Settings
@implementation Settings
+ (instancetype)build {
Settings* instance = [[self alloc] init];
instance.groups = [[NSMutableArray alloc]init];
instance.configs = [[NSMutableArray alloc]init];
// configs has at least one group
[instance.configs addObject:[[NSMutableArray alloc] init]];
return instance;
}
- (void)setItem:(SettingItem*)item {
if (item.type == kSettingGroup) {
// group type
for (int i = 0; i < _groups.count; ++i) {
SettingItem* groupItem = _groups[i];
if ([groupItem.key isEqualToString:item.key]) {
// same group exist, do nothing
return;
}
}
[_groups addObject:item];
NSMutableArray* group = _configs[0];
if (_configs.count == 1 && group.count == 0) {
// first group has no items yet, means this is first group
return;
}
// append new group
[_configs addObject:[[NSMutableArray alloc] init]];
return;
} else {
// other type
NSMutableArray* group = NULL;
for (int i = 0; i < _configs.count; ++i) {
group = _configs[i];
for (int j = 0; j < group.count; ++j) {
SettingItem* cur = group[j];
if ([cur.key isEqualToString:item.key]) {
_configs[i][j] = item;
return;
}
}
}
// default add to last group
[group addObject:item];
}
}
- (SettingItem*)getItem:(NSString*)key type:(SettingType)type {
if (type == kSettingGroup) {
// group type
for (int i = 0; i < _groups.count; ++i) {
SettingItem* cur = _groups[i];
if ([cur.key isEqualToString:key]) {
return cur;
}
}
return NULL;
} else {
// other type
for (int i = 0; i < _configs.count; ++i) {
NSMutableArray* group = _configs[i];
for (int j = 0; j < group.count; ++j) {
SettingItem* cur = group[j];
if ([cur.key isEqualToString:key]) {
if (cur.type == type) {
return cur;
}
return NULL;
}
}
}
return NULL;
}
return NULL;
}
- (void)registerItems:(NSArray*)cfgs {
for (int i = 0; i < cfgs.count; ++i) {
[self setItem:cfgs[i]];
}
}
- (void)setBool:(NSString*)key {
[self setBool:key val:false];
}
- (void)setBool:(NSString*)key val:(BOOL)val {
[self setItem:[SettingItem buildBool:key val:val hint:@""]];
}
- (void)setInt:(NSString*)key {
[self setInt:key val:0];
}
- (void)setInt:(NSString*)key val:(int)val {
[self setItem:[SettingItem buildInt:key val:val hint:@""]];
}
- (void)setDouble:(NSString*)key {
[self setDouble:key val:0.];
}
- (void)setDouble:(NSString*)key val:(double)val {
[self setItem:[SettingItem buildDouble:key val:val hint:@""]];
}
- (void)setString:(NSString*)key {
[self setString:key val:@""];
}
- (void)setString:(NSString*)key val:(NSString*)val {
[self setItem:[SettingItem buildString:key val:val hint:@""]];
}
- (void)setOptions:(NSString*)key {
[self setOptions:key val:[SettingOptions build:@[] choose:0]];
}
- (void)setOptions:(NSString*)key val:(SettingOptions*)val {
[self setItem:[SettingItem buildOptions:key val:val hint:@""]];
}
- (BOOL)getBool:(NSString*)key {
return [self getBool:key def:false];
}
- (BOOL)getBool:(NSString*)key def:(BOOL)def {
SettingItem* item = [self getItem:key type:kSettingBool];
if (item != NULL) {
NSNumber* val = (NSNumber*)item.value;
return [val intValue] == 1;
}
return def;
}
- (int)getInt:(NSString*)key {
return [self getInt:key def:0];
}
- (int)getInt:(NSString*)key def:(int)def {
SettingItem* item = [self getItem:key type:kSettingNumber];
if (item != NULL) {
NSNumber* val = (NSNumber*)item.value;
return [val intValue];
}
return def;
}
- (double)getDouble:(NSString*)key {
return [self getDouble:key def:0];
}
- (double)getDouble:(NSString*)key def:(double)def {
SettingItem* item = [self getItem:key type:kSettingNumber];
if (item != NULL) {
NSNumber* val = (NSNumber*)item.value;
return [val doubleValue];
}
return def;
}
- (NSString*)getString:(NSString*)key {
return [self getString:key def:@""];
}
- (NSString*)getString:(NSString*)key def:(NSString*)def {
SettingItem* item = [self getItem:key type:kSettingString];
if (item != NULL) {
return (NSString*)item.value;
}
return def;
}
- (SettingOptions*)getOptions:(NSString*)key {
return [self getOptions:key def:[SettingOptions build:@[] choose:0]];
}
- (SettingOptions*)getOptions:(NSString*)key def:(SettingOptions*)def {
SettingItem* item = [self getItem:key type:kSettingOptions];
if (item != NULL) {
return (SettingOptions*)item.value;
}
return def;
}
- (NSString*)getOptionsValue:(NSString*)key {
SettingItem* item = [self getItem:key type:kSettingOptions];
if (item != NULL) {
SettingOptions *options = (SettingOptions*)item.value;
return options.optionsArray[options.chooseIdx];
}
return @"";
}
@end

View File

@ -0,0 +1,207 @@
//
// SettingsHelper.h
// SpeechDemo
//
// Created by bytedance on 2020/9/9.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Settings.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - SETTING_KEY
// view id
extern NSString *const VIEW_ASR;
extern NSString *const VIEW_ASR_OFFLINE;
extern NSString *const VIEW_AU;
extern NSString *const VIEW_BIGASR;
extern NSString *const VIEW_AFP;
extern NSString *const VIEW_CAPT;
extern NSString *const VIEW_FULLLINK;
extern NSString *const VIEW_TTS;
extern NSString *const VIEW_BITTS;
extern NSString *const VIEW_UNITTS;
extern NSString *const VIEW_VOICECLONE;
extern NSString *const VIEW_VOICECONV;
extern NSString *const VIEW_DIALOG;
extern NSString *const VIEW_DIALOG_DELEGATE;
extern NSString *const VIEW_VAD;
extern NSString *const VIEW_KWS;
extern NSString *const VIEW_TEST_AFP;
extern NSString *const VIEW_TEST_ASR_OFFLINE_RTF;
extern NSString *const VIEW_TEST_ASR_STRESS;
// common
extern NSString *const SETTING_COMMON;
extern NSString *const SETTING_APPID;
extern NSString *const SETTING_APPKEY;
extern NSString *const SETTING_TOKEN;
extern NSString *const SETTING_CLUSTER;
extern NSString *const SETTING_RESOURCE_ID;
extern NSString *const SETTING_ADDRESS;
extern NSString *const SETTING_URI;
extern NSString *const SETTING_VOICE;
extern NSString *const SETTING_VOICE_TYPE;
extern NSString *const SETTING_ONLINE_VOICE;
extern NSString *const SETTING_ONLINE_VOICE_TYPE;
extern NSString *const SETTING_OFFLINE_VOICE;
extern NSString *const SETTING_OFFLINE_VOICE_TYPE;
extern NSString *const SETTING_GET_VOLUME;
extern NSString *const SETTING_RECORD_TYPE;
extern NSString *const SETTING_RECORD_SAVE;
extern NSString *const SETTING_RECORD_FILE_TYPE;
extern NSString *const SETTING_SAMPLE_RATE;
extern NSString *const SETTING_CHANNEL;
extern NSString *const SETTING_ENABLE_AEC;
extern NSString *const SETTING_RESTART_AUDIO_SESSION_ENABLE;
extern NSString *const SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE;
extern NSString *const SETTING_LICENSE_NAME;
extern NSString *const SETTING_LICENSE_BUSI_ID;
extern NSString *const SETTING_AUTHENTICATION_TYPE;
extern NSString *const SETTING_BUSINESS_KEY;
extern NSString *const SETTING_AUTHENTICATE_SECRET;
extern NSString *const SETTING_DISABLE_WS_RECONNECT;
extern NSString *const SETTING_AUDIO_FADEOUT_DURATION;
extern NSString *const SETTING_VAD_MAX_SPEECH_DURATION;
extern NSString *const SETTING_VAD_MAX_MUSIC_DURATION;
extern NSString *const SETTING_STREAM_PACKAGE_DURATION;
extern NSString *const SETTING_REQUEST_HEADERS;
extern NSString *const SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK;
// asr
extern NSString *const SETTING_ASR;
extern NSString *const SETTING_ASR_RECORDER_SAVE;
extern NSString *const SETTING_ASR_ENABLE_DDC;
extern NSString *const SETTING_ASR_ENABLE_ITN;
extern NSString *const SETTING_ASR_ENABLE_NLU_PUNC;
extern NSString *const SETTING_ASR_DISABLE_END_PUNC;
extern NSString *const SETTING_ASR_KEEP_RECORDING;
extern NSString *const SETTING_ASR_HOTWORDS;
extern NSString *const SETTING_ASR_CORRECTWORDS;
extern NSString *const SETTING_ASR_VAD_START_SILENCE_TIME;
extern NSString *const SETTING_ASR_VAD_END_SILENCE_TIME;
extern NSString *const SETTING_ASR_VAD_MODE;
extern NSString *const SETTING_ASR_RESULT_TYPE;
extern NSString *const SETTING_ASR_MAX_RETRY_TIMES;
extern NSString *const SETTING_ASR_SHOW_LANGUAGE;
extern NSString *const SETTING_ASR_LANGUAGE;
extern NSString *const SETTING_ASR_AUTO_STOP;
extern NSString *const SETTING_ASR_MODEL_NAME;
extern NSString *const SETTING_ASR_REQ_PARAMS;
extern NSString *const SETTING_ASR_ENABLE_RESOURCE_DOWNLOAD;
// capt
extern NSString *const SETTING_CAPT;
extern NSString *const SETTING_CAPT_RECORDER_SAVE;
extern NSString *const SETTING_CAPT_STREAMING_MODE;
extern NSString *const SETTING_CAPT_CORE_TYPE;
extern NSString *const SETTING_CAPT_DIFFICULTY_LEVEL;
// fulllink
extern NSString *const SETTING_FULLLINK;
extern NSString *const SETTING_FULLLINK_ENGINE_TYPE;
extern NSString *const SETTING_FULLLINK_WAKEUP_WORDS;
extern NSString *const SETTING_FULLLINK_ENABLE_RECORDER_DUMP;
extern NSString *const SETTING_FULLLINK_ENABLE_KWS_DUMP;
extern NSString *const SETTING_FULLLINK_WAKEUP_MODE;
extern NSString *const SETTING_FULLLINK_ONLY_ASR;
extern NSString *const SETTING_FULLLINK_DISABLE_TTS;
extern NSString *const SETTING_FULLLINK_DISABLE_SIGNAL;
extern NSString *const SETTING_FULLLINK_DISABLE_DEVICE_TYPE;
extern NSString *const SETTING_FULLLINK_DISABLE_SIGTHREAD_PRI;
extern NSString *const SETTING_FULLLINK_DISABLE_FILE_OR_DIRECTORY_NAME;
// tts
extern NSString *const SETTING_TTS;
extern NSString *const SETTING_TTS_ONLINE_LANGUAGE;
extern NSString *const SETTING_TTS_OFFLINE_LANGUAGE;
extern NSString *const SETTING_PREVENT_PLAYER_CREATION;
extern NSString *const SETTING_TTS_ENABLE_RESUME_FROM_BREAKPOINT;
extern NSString *const SETTING_TTS_ENABLE_PLAYER;
extern NSString *const SETTING_TTS_ENABLE_DUMP;
extern NSString *const SETTING_TTS_ENABLE_DATA_CALLBACK;
extern NSString *const SETTING_TTS_ENABLE_WORD_LEVEL_PROGRESS_UPDATE;
extern NSString *const SETTING_TTS_ENABLE_CACHE;
extern NSString *const SETTING_TTS_WITH_INTENT;
extern NSString *const SETTING_TTS_LIMIT_CPU_USAGE;
extern NSString *const SETTING_TTS_TEXT_TYPE;
extern NSString *const SETTING_TTS_WORK_MODE;
extern NSString *const SETTING_TTS_SPEAK_SPEED;
extern NSString *const SETTING_TTS_AUDIO_VOLUME;
extern NSString *const SETTING_TTS_AUDIO_PITCH;
extern NSString *const SETTING_TTS_SAMPLE_RATE;
extern NSString *const SETTING_TTS_EMOTION;
extern NSString *const SETTING_TTS_MODEL_NAME;
extern NSString *const SETTING_TTS_SILENCE_DURATION;
extern NSString *const SETTING_TTS_OFFLINE_RESOURCE_FORMAT;
extern NSString *const SETTING_TTS_BACKEND_CLUSTER;
extern NSString *const SETTING_TTS_REQUEST_ID;
extern NSString *const SETTING_TTS_REQUEST_PARAMS;
extern NSString *const SETTING_TTS_SILENCE_DURATION;
extern NSString *const SETTING_TTS_USE_VOICECLONE_VOICE;
// voiceclone
extern NSString *const SETTING_VOICECLONE;
extern NSString *const SETTING_VOICECLONE_ADDRESS;
extern NSString *const SETTING_VOICECLONE_STREAM_ADDRESS;
extern NSString *const SETTING_VOICECLONE_UID;
extern NSString *const SETTING_VOICECLONE_QUERY_UIDS;
extern NSString *const SETTING_VOICECLONE_VOICE_TYPE;
extern NSString *const SETTING_VOICECLONE_GENDER;
extern NSString *const SETTING_VOICECLONE_TASKID;
// voiceconv
extern NSString *const SETTING_VOICECONV;
extern NSString *const SETTING_VOICECONV_RESULT_SAMPLE_RATE;
extern NSString *const SETTING_VOICECONV_ENABLE_RECORD_DUMP;
extern NSString *const SETTING_VOICECONV_ENABLE_RESULT_DUMP;
extern NSString *const SETTING_VOICECONV_REQUEST_INTERVAL;
// dialog
extern NSString *const SETTING_DIALOG;
extern NSString *const SETTING_DIALOG_ENABLE_RECORDER_DUMP;
extern NSString *const SETTING_DIALOG_ENABLE_PLAYER_DUMP;
extern NSString *const SETTING_DIALOG_BOT_NAME;
// vad
extern NSString *const SETTING_VAD;
extern NSString *const SETTING_VAD_RECORDER_SAVE;
extern NSString *const SETTING_VAD_HEAD_SILENCE_THRESHOLD;
extern NSString *const SETTING_VAD_TAIL_SILENCE_THRESHOLD;
// au
extern NSString *const SETTING_AU;
extern NSString *const SETTING_AU_ABILITY;
extern NSString *const SETTING_AU_RECORDER_SAVE;
extern NSString *const SETTING_AU_PROCESS_TIMEOUT;
extern NSString *const SETTING_AU_AUDIO_PACKET_DURATION;
extern NSString *const SETTING_AU_EMPTY_PACKET_INTERVAL;
// afp
extern NSString *const SETTING_AFP;
extern NSString *const SETTING_MUSIC_ENGINE_NAME;
extern NSString *const SETTING_AFP_RESULT_TYPE;
extern NSString *const SETTING_AFP_INSTANCE_NUMBER;
// kws
extern NSString *const SETTING_KWS;
extern NSString *const SETTING_KWS_CUSTOM_WORDS;
extern NSString *const SETTING_KWS_MODEL_NAME;
// asr test
extern NSString *const SETTING_ASR_STRESS;
extern NSString *const SETTING_ASR_STRESS_SCENEID;
#pragma mark - SETTING_HELPER
@interface SettingsHelper : NSObject
+ (instancetype)shareInstance;
- (Settings*)getSettings:(NSString*)engine;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,804 @@
//
// SettingsHelper.m
// SpeechDemo
//
// Created by bytedance on 2020/9/9.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface SettingsHelper()
@property (strong, nonatomic) Settings* asrSettings;
@property (strong, nonatomic) Settings* asrOfflineSettings;
@property (strong, nonatomic) Settings* bigAsrSettings;
@property (strong, nonatomic) Settings* captSettings;
@property (strong, nonatomic) Settings* fulllinkSettings;
@property (strong, nonatomic) Settings* ttsSettings;
@property (strong, nonatomic) Settings* voiceCloneSettings;
@property (strong, nonatomic) Settings* voiceConvSettings;
@property (strong, nonatomic) Settings* dialogSettings;
@property (strong, nonatomic) Settings* bittsSettings;
@property (strong, nonatomic) Settings* uniTtsSettings;
@property (strong, nonatomic) Settings* dialogDelegateSettings;
@property (strong, nonatomic) Settings* vadSettings;
@property (strong, nonatomic) Settings* auSettings;
@property (strong, nonatomic) Settings* afpSettings;
@property (strong, nonatomic) Settings* kwsSettings;
@property (strong, nonatomic) Settings* testAfpSettings;
@property (strong, nonatomic) Settings* testAsrOfflineRtfSettings;
@property (strong, nonatomic) Settings* testAsrStressSettings;
@end
@implementation SettingsHelper
#pragma mark - SETTING_KEY
// view id
NSString *const VIEW_ASR = @"ASR";
NSString *const VIEW_ASR_OFFLINE = @"ASR_OFFLINE";
NSString *const VIEW_AU = @"AU";
NSString *const VIEW_BIGASR = @"BIGASR";
NSString *const VIEW_AFP = @"AFP";
NSString *const VIEW_CAPT = @"CAPT";
NSString *const VIEW_FULLLINK = @"FULLLINK";
NSString *const VIEW_TTS = @"TTS";
NSString *const VIEW_BITTS = @"BITTS";
NSString *const VIEW_UNITTS = @"UNITTS";
NSString *const VIEW_VOICECLONE = @"VOICECLONE";
NSString *const VIEW_VOICECONV = @"VOICECONV";
NSString *const VIEW_DIALOG = @"DIALOG";
NSString *const VIEW_DIALOG_DELEGATE = @"DIALOG_DELEGATE";
NSString *const VIEW_VAD = @"VAD";
NSString *const VIEW_KWS = @"KWS";
NSString *const VIEW_TEST_AFP = @"AFP_TEST";
NSString *const VIEW_TEST_ASR_OFFLINE_RTF = @"TEST_ASR_OFFLINE_RTF";
NSString *const VIEW_TEST_ASR_STRESS = @"TEST_ASR_STRESS";
// common
NSString *const SETTING_COMMON = @"Common";
NSString *const SETTING_APPID = @"AppID";
NSString *const SETTING_APPKEY = @"AppKey";
NSString *const SETTING_TOKEN = @"Token";
NSString *const SETTING_CLUSTER = @"Cluster";
NSString *const SETTING_RESOURCE_ID = @"ResourceId";
NSString *const SETTING_ADDRESS = @"Address";
NSString *const SETTING_URI = @"Uri";
NSString *const SETTING_VOICE = @"Voice";
NSString *const SETTING_VOICE_TYPE = @"Voice Type";
NSString *const SETTING_ONLINE_VOICE = @"Online Voice";
NSString *const SETTING_ONLINE_VOICE_TYPE = @"Online Voice Type";
NSString *const SETTING_OFFLINE_VOICE = @"Offline Voice";
NSString *const SETTING_OFFLINE_VOICE_TYPE = @"Offline Voice Type";
NSString *const SETTING_GET_VOLUME = @"Get Volume";
NSString *const SETTING_RECORD_TYPE = @"Record Type";
NSString *const SETTING_RECORD_SAVE = @"Record Save";
NSString *const SETTING_RECORD_FILE_TYPE = @"Record File Type";
NSString *const SETTING_SAMPLE_RATE = @"Sample Rate";
NSString *const SETTING_CHANNEL = @"Channel";
NSString *const SETTING_ENABLE_AEC = @"Enable Aec";
NSString *const SETTING_RESTART_AUDIO_SESSION_ENABLE = @"Restart AudioSession";
NSString *const SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE = @"Resume Others Interrupted Playback";
NSString *const SETTING_AUTHENTICATION_TYPE = @"Authentication Type";
NSString *const SETTING_BUSINESS_KEY = @"Business Key";
NSString *const SETTING_AUTHENTICATE_SECRET = @"Authenticate Secret";
NSString *const SETTING_DISABLE_WS_RECONNECT = @"Disable Ws Reconnect";
NSString *const SETTING_AUDIO_FADEOUT_DURATION = @"Audio Fade Out Duration";
NSString *const SETTING_VAD_MAX_SPEECH_DURATION = @"Vad Max Speech Duration";
NSString *const SETTING_VAD_MAX_MUSIC_DURATION = @"Vad Max Music Duration";
NSString *const SETTING_STREAM_PACKAGE_DURATION = @"Stream Package Duration";
NSString *const SETTING_REQUEST_HEADERS = @"Request headers";
NSString *const SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK = @"Enable player audio callback";
// asr
NSString *const SETTING_ASR = @"Asr";
NSString *const SETTING_ASR_RECORDER_SAVE = @"Asr Recorder Save";
NSString *const SETTING_ASR_ENABLE_DDC = @"Asr Enable Ddc";
NSString *const SETTING_ASR_ENABLE_ITN = @"Asr Enable Itn";
NSString *const SETTING_ASR_ENABLE_NLU_PUNC = @"Asr Enable nlu punctuation";
NSString *const SETTING_ASR_DISABLE_END_PUNC = @"asr_disable_end_punc";
NSString *const SETTING_ASR_KEEP_RECORDING = @"Asr Enable Keep Recording";
NSString *const SETTING_ASR_HOTWORDS = @"Asr HotWords";
NSString *const SETTING_ASR_CORRECTWORDS = @"Asr CorrectWords";
NSString *const SETTING_ASR_VAD_START_SILENCE_TIME = @"Asr Vad Start Silence Time";
NSString *const SETTING_ASR_VAD_END_SILENCE_TIME = @"Asr Vad End Silence Time";
NSString *const SETTING_ASR_VAD_MODE = @"Asr VAD Mode";
NSString *const SETTING_ASR_RESULT_TYPE = @"Asr Result Type";
NSString *const SETTING_ASR_MAX_RETRY_TIMES = @"Asr Max Retry Times";
NSString *const SETTING_ASR_SHOW_LANGUAGE = @"Asr Show Language";
NSString *const SETTING_ASR_LANGUAGE = @"Asr Language";
NSString *const SETTING_ASR_AUTO_STOP = @"Asr Auto Stop";
NSString *const SETTING_ASR_MODEL_NAME = @"Asr Model Name";
NSString *const SETTING_ASR_REQ_PARAMS = @"Asr Request Params";
NSString *const SETTING_ASR_ENABLE_RESOURCE_DOWNLOAD = @"Enable Asr Resource Download";
// capt
NSString *const SETTING_CAPT = @"Capt";
NSString *const SETTING_CAPT_RECORDER_SAVE = @"Capt Recorder Save";
NSString *const SETTING_CAPT_STREAMING_MODE = @"Capt Streaming Mode";
NSString *const SETTING_CAPT_CORE_TYPE = @"Capt Core Type";
NSString *const SETTING_CAPT_DIFFICULTY_LEVEL = @"Capt Difficulty Level";
// fulllink
NSString *const SETTING_FULLLINK = @"Fulllink";
NSString *const SETTING_FULLLINK_ENGINE_TYPE = @"Fulllink Engine Type";
NSString *const SETTING_FULLLINK_WAKEUP_WORDS = @"Fulllink Wakeup Words";
NSString *const SETTING_FULLLINK_ENABLE_RECORDER_DUMP = @"Fulllink Enable Recorder Dump";
NSString *const SETTING_FULLLINK_ENABLE_KWS_DUMP = @"Fulllink Enable Kws Dump";
NSString *const SETTING_FULLLINK_WAKEUP_MODE = @"Fulllink Wakeup Mode";
NSString *const SETTING_FULLLINK_ONLY_ASR = @"Fulllink Only Asr";
NSString *const SETTING_FULLLINK_DISABLE_TTS = @"Fulllink Disable Tts";
NSString *const SETTING_FULLLINK_DISABLE_SIGNAL = @"Fulllink Disable Signal";
NSString *const SETTING_FULLLINK_DISABLE_DEVICE_TYPE = @"Fulllink Device Type";
NSString *const SETTING_FULLLINK_DISABLE_SIGTHREAD_PRI = @"Fulllink Disable SigThread Pri";
NSString *const SETTING_FULLLINK_DISABLE_FILE_OR_DIRECTORY_NAME = @"Fulllink Disable File Or Directory Name";
// tts
NSString *const SETTING_TTS = @"Tts";
NSString *const SETTING_TTS_ONLINE_LANGUAGE = @"Tts online Language";
NSString *const SETTING_TTS_EMOTION = @"Tts emotion";
NSString *const SETTING_TTS_OFFLINE_LANGUAGE = @"Tts offline Language";
NSString *const SETTING_PREVENT_PLAYER_CREATION = @"Prevent Player Creation";
NSString *const SETTING_TTS_ENABLE_RESUME_FROM_BREAKPOINT = @"Tts Enable Resume From Breakpoint";
NSString *const SETTING_TTS_ENABLE_PLAYER = @"Tts Enable Player";
NSString *const SETTING_TTS_ENABLE_DUMP = @"Tts Enable Dump";
NSString *const SETTING_TTS_ENABLE_DATA_CALLBACK = @"Tts Enable Data Callback";
NSString *const SETTING_TTS_ENABLE_WORD_LEVEL_PROGRESS_UPDATE = @"Tts Enable Word level Progress Update";
NSString *const SETTING_TTS_ENABLE_CACHE = @"Tts Enable Cache";
NSString *const SETTING_TTS_WITH_INTENT = @"Tts With Intent Predication";
NSString *const SETTING_TTS_LIMIT_CPU_USAGE = @"Tts Limit Cpu Usage";
NSString *const SETTING_TTS_TEXT_TYPE = @"Tts Text Type";
NSString *const SETTING_TTS_WORK_MODE = @"Tts Synthesis Mode";
NSString *const SETTING_TTS_SPEAK_SPEED = @"Tts Speak Speed";
NSString *const SETTING_TTS_AUDIO_VOLUME = @"Tts Audio Volume";
NSString *const SETTING_TTS_AUDIO_PITCH = @"Tts Audio Pitch";
NSString *const SETTING_TTS_SAMPLE_RATE = @"Tts Sample Rate";
NSString *const SETTING_TTS_MODEL_NAME = @"Tts Model Name";
NSString *const SETTING_LICENSE_NAME = @"License Name";
NSString *const SETTING_LICENSE_BUSI_ID = @"Busi Id";
NSString *const SETTING_TTS_BACKEND_CLUSTER = @"Backend Cluster";
NSString *const SETTING_TTS_REQUEST_ID = @"Tts Request ID";
NSString *const SETTING_TTS_REQUEST_PARAMS = @"Tts Request Parameters";
NSString *const SETTING_TTS_USE_VOICECLONE_VOICE = @"Tts Use VoiceClone Voice";
NSString *const SETTING_TTS_SILENCE_DURATION = @"Tts Silence Duration";
NSString *const SETTING_TTS_OFFLINE_RESOURCE_FORMAT = @"TTS Offline Model Format";
// voiceclone
NSString *const SETTING_VOICECLONE = @"VoiceClone";
NSString *const SETTING_VOICECLONE_ADDRESS = @"VoiceClone Address";
NSString *const SETTING_VOICECLONE_STREAM_ADDRESS = @"VoiceClone Stream Address";
NSString *const SETTING_VOICECLONE_UID = @"VoiceClone Uid";
NSString *const SETTING_VOICECLONE_QUERY_UIDS = @"VoiceClone Query Uids";
NSString *const SETTING_VOICECLONE_VOICE_TYPE = @"VoiceClone Voice Type";
NSString *const SETTING_VOICECLONE_GENDER = @"VoiceClone Gender (Is Female)";
NSString *const SETTING_VOICECLONE_TASKID = @"VoiceClone Taskid";
// voiceconv
NSString *const SETTING_VOICECONV = @"VoiceConv";
NSString *const SETTING_VOICECONV_RESULT_SAMPLE_RATE = @"VoiceConv Result Sample Rate";
NSString *const SETTING_VOICECONV_ENABLE_RECORD_DUMP = @"VoiceConv Enable Record Dump";
NSString *const SETTING_VOICECONV_ENABLE_RESULT_DUMP = @"VoiceConv Enable Result Dump";
NSString *const SETTING_VOICECONV_REQUEST_INTERVAL = @"VoiceConv Request Interval (Unit: ms)";
// dialog
NSString *const SETTING_DIALOG = @"Dialog";
NSString *const SETTING_DIALOG_ENABLE_RECORDER_DUMP = @"Dialog Recorder Dump";
NSString *const SETTING_DIALOG_ENABLE_PLAYER_DUMP = @"Dialog Player Dump";
NSString *const SETTING_DIALOG_BOT_NAME = @"Dialog Bot Name";
// bitts
NSString *const SETTING_BITTS = @"BiTTS";
// unitts
NSString *const SETTING_UNITTS = @"UniTTS";
// vad
NSString *const SETTING_VAD = @"Vad";
NSString *const SETTING_VAD_RECORDER_SAVE = @"Vad Recorder Save";
NSString *const SETTING_VAD_HEAD_SILENCE_THRESHOLD = @"Vad Head silence Threshold";
NSString *const SETTING_VAD_TAIL_SILENCE_THRESHOLD = @"Vad Tail silence Threshold";
// au
NSString *const SETTING_AU = @"Au";
NSString *const SETTING_AU_ABILITY = @"Au Ability";
NSString *const SETTING_AU_RECORDER_SAVE = @"Au Recorder Save";
NSString *const SETTING_AU_PROCESS_TIMEOUT = @"Au Process Timeout";
NSString *const SETTING_AU_AUDIO_PACKET_DURATION = @"Au Audio Packet Duration";
NSString *const SETTING_AU_EMPTY_PACKET_INTERVAL = @"Au Empty Packet Interval";
// afp
NSString *const SETTING_AFP = @"Afp";
NSString *const SETTING_MUSIC_ENGINE_NAME = @"Music engine name";
NSString *const SETTING_AFP_RESULT_TYPE = @"Afp result type";
NSString *const SETTING_AFP_INSTANCE_NUMBER = @"Afp instance number";
// kws
NSString *const SETTING_KWS = @"Kws";
NSString *const SETTING_KWS_CUSTOM_WORDS = @"Kws Custom Words";
NSString *const SETTING_KWS_MODEL_NAME = @"Kws Model Name";
// asr test
NSString *const SETTING_ASR_STRESS = @"Asr Stress";
NSString *const SETTING_ASR_STRESS_SCENEID = @"Asr Stress Sceneid";
#pragma mark - SETTING_HINT
static NSString *const SETTING_HINT_CLUSTER = @"Cluster";
static NSString *const SETTING_HINT_BACKEND_CLUSTER = @"Backend Cluster";
static NSString *const SETTING_HINT_ADDRESS = @"Address";
static NSString *const SETTING_HINT_URI = @"Uri";
static NSString *const SETTING_HINT_VOICE = @"Voice";
static NSString *const SETTING_HINT_VOICE_TYPE = @"Voice Type";
static NSString *const SETTING_HINT_ONLINE_VOICE = @"Online Voice";
static NSString *const SETTING_HINT_ONLINE_VOICE_TYPE = @"Online Voice Type";
static NSString *const SETTING_HINT_OFFLINE_VOICE = @"Offline Voice";
static NSString *const SETTING_HINT_OFFLINE_VOICE_TYPE = @"Offline Voice Type";
static NSString *const SETTING_HINT_TTS_ONLINE_LANGUAGE = @"Tts online Language";
static NSString *const SETTING_HINT_TTS_EMOTION = @"Tts emotion";
static NSString *const SETTING_HINT_TTS_OFFLINE_LANGUAGE = @"Tts offline Language";
static NSString *const SETTING_HINT_DIFFICULTY_LEVEL = @"Support 1,2,3";
static NSString *const SETTING_HINT_VOICECLONE_UID = @"VoiceClone Uid";
static NSString *const SETTING_HINT_VOICECLONE_QUERY_UIDS = @"VoiceClone Query Uids";
static NSString *const SETTING_HINT_VOICECLONE_VOICE_TYPE = @"VoiceClone Voice Type";
static NSString *const SETTING_HINT_BUSINESS_KEY = @"Business Key";
static NSString *const SETTING_HINT_AUTHENTICATE_SECRET = @"Authenticate Secret";
static NSString *const SETTING_HINT_LICENSE_NAME = @"Input license name";
static NSString *const SETTING_HINT_LICENSE_BUSI_ID = @"Input busi id";
static NSString *const SETTING_HINT_TTS_SAMPLE_RATE = @"Input tts sample rate";
static NSString *const SETTING_HINT_TTS_MODEL_NAME = @"Input Tts Model Name";
static NSString *const SETTING_HINT_MAX_CACHE_NUM = @"Input max number of client audio cache, such as 100.";
static NSString *const SETTING_HINT_TTS_CACHE_RENEWAL_DURATION = @"Input cache renewal duration, unit:ms";
#pragma mark - SETTING_OPTIONS
static NSArray* SETTING_OPTIONS_RECORD_TYPE(void) {
return @[@"Recorder", @"File", @"Stream"];
}
static NSArray* SETTING_OPTIONS_RECORD_FILE_TYPE(void) {
return @[@"Wav", @"Aac"];
}
static NSArray* SETTING_OPTIONS_ASR_RESULT_TYPE(void) {
return @[@"full", @"single"];
}
static NSArray* SETTING_OPTIONS_CAPT_CORE_TYPE(void) {
return @[@"English sentence score", @"English word score", @"English word pronounce", @"Chinese sentence raw"];
}
static NSArray* SETTING_OPTIONS_FULLLINK_ENGINE_TYPE(void) {
return @[@"FULLLINK LITE", @"FULLLINK"];
}
static NSArray* SETTING_OPTIONS_FULLLINK_WAKEUP_WORDS(void) {
return @[@"大力大力only", @"大力大力(大力同学)", @"大力同学(大力大力)", @"大力同学only"];
}
static NSArray* SETTING_OPTIONS_FULLLINK_WAKEUP_MODE(void) {
return @[@"normal", @"disable wakeup", @"night mode"];
}
static NSArray* SETTING_OPTIONS_TTS_TEXT_TYPE(void) {
return @[@"Plain", @"SSML", @"Json"];
}
static NSArray* SETTING_OPTIONS_TTS_WORK_MODE(void) {
return @[@"Online", @"Offline", @"Alternate"];
}
static NSArray* SETTING_OPTIONS_TTS_OFFLINE_RESOURCE_FORMAT(void) {
return @[@"SingleVoice", @"MultipleVoice"];
}
static NSArray* SETTING_OPTIONS_AUTHENTICATION_TYPE(void) {
return @[@"Pre Bind", @"Late Bind"];
}
static NSArray* SETTING_OPTIONS_AU_ABILITY(void) {
return @[@"ASR", @"MUSIC", @"ASR+MUSIC"];
}
static NSArray* SETTING_OPTIONS_MUSIC_ENGINE_NAME(void) {
return @[@"AFP", @"CoverSong", @"Humming"];
}
static NSArray* SETTING_OPTIONS_AFP_RESULT_TYPE(void) {
return @[@"Bytes", @"Json"];
}
static NSArray* SETTING_OPTIONS_ASR_STRESS_TYPE(void) {
return @[@"正常场景1", @"正常场景2", @"ERROR回调时析构", @"随机压测"];
}
#pragma mark - Setting Init
+ (instancetype)shareInstance {
static SettingsHelper* sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
// Asr settings
sharedInstance.asrSettings = [Settings build];
[sharedInstance.asrSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_TOKEN hint:@""],
[SettingItem buildString:SETTING_CLUSTER val:SDEF_ASR_DEFAULT_CLUSTER hint:SETTING_HINT_CLUSTER],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_ASR_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildInt:SETTING_SAMPLE_RATE val:16000 hint:@""],
[SettingItem buildInt:SETTING_CHANNEL val:1 hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_RESTART_AUDIO_SESSION_ENABLE val:false hint:@""],
[SettingItem buildBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE val:false hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_SPEECH_DURATION val:60000 hint:@""],
[SettingItem buildString:SETTING_REQUEST_HEADERS val:@"{\"custom_header_key0\": \"custom_header_value0\",\"custom_header_key1\": \"custom_header_value1\"}" hint:@""],
// asr
[SettingItem buildGroup:SETTING_ASR val:@"" hint:@""],
[SettingItem buildBool:SETTING_ASR_RECORDER_SAVE val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_DDC val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_ITN val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_NLU_PUNC val:true hint:@""],
[SettingItem buildBool:SETTING_ASR_DISABLE_END_PUNC val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_KEEP_RECORDING val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_SHOW_LANGUAGE val:false hint:@""],
[SettingItem buildString:SETTING_ASR_LANGUAGE val:@"en-US" hint:@""],
[SettingItem buildString:SETTING_ASR_HOTWORDS val:@"{\"hotwords\":[{\"word\":\"\",\"scale\":\"2.0\"}]}" hint:@""],
[SettingItem buildString:SETTING_ASR_CORRECTWORDS val:@"{\"\":\"\",\"\":\"\",\"\":\"\",\"\":\"\"}" hint:@""],
[SettingItem buildInt:SETTING_ASR_VAD_START_SILENCE_TIME val:0 hint:@""],
[SettingItem buildInt:SETTING_ASR_VAD_END_SILENCE_TIME val:0 hint:@""],
[SettingItem buildString:SETTING_ASR_VAD_MODE val:@"" hint:@""],
[SettingItem buildOptions:SETTING_ASR_RESULT_TYPE val:[SettingOptions build:SETTING_OPTIONS_ASR_RESULT_TYPE() choose:0] hint:@""],
[SettingItem buildInt:SETTING_ASR_MAX_RETRY_TIMES val:0 hint:@""],
[SettingItem buildString:SETTING_ASR_REQ_PARAMS val:@"" hint:@""],
]];
// BigAsr settings
sharedInstance.bigAsrSettings = [Settings build];
[sharedInstance.bigAsrSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_BIGASR_DEFAULT_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_BIGASR_DEFAULT_TOKEN hint:@""],
[SettingItem buildString:SETTING_RESOURCE_ID val:SDEF_BIGASR_DEFAULT_RESOURCE_ID hint:@""],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_BIGASR_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildInt:SETTING_SAMPLE_RATE val:16000 hint:@""],
[SettingItem buildInt:SETTING_CHANNEL val:1 hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_RESTART_AUDIO_SESSION_ENABLE val:false hint:@""],
[SettingItem buildBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE val:false hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_SPEECH_DURATION val:60000 hint:@""],
// asr
[SettingItem buildGroup:SETTING_ASR val:@"" hint:@""],
[SettingItem buildBool:SETTING_ASR_RECORDER_SAVE val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_DDC val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_ITN val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_NLU_PUNC val:true hint:@""],
[SettingItem buildString:SETTING_ASR_REQ_PARAMS val:@"" hint:@""],
]];
// Asr offline settings
sharedInstance.asrOfflineSettings = [Settings build];
[sharedInstance.asrOfflineSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_SPEECH_DURATION val:15000 hint:@""],
[SettingItem buildOptions:SETTING_AUTHENTICATION_TYPE val:[SettingOptions build:SETTING_OPTIONS_AUTHENTICATION_TYPE() choose:1] hint:@""],
[SettingItem buildString:SETTING_LICENSE_NAME val:SDEF_LICENSE_NAME hint:SETTING_HINT_LICENSE_NAME],
[SettingItem buildString:SETTING_LICENSE_BUSI_ID val:SDEF_LICENSE_BUSI_ID hint:SETTING_HINT_LICENSE_BUSI_ID],
[SettingItem buildString:SETTING_BUSINESS_KEY val:SDEF_BUSINESS_KEY hint:SETTING_HINT_BUSINESS_KEY],
[SettingItem buildString:SETTING_AUTHENTICATE_SECRET val:SDEF_SECRET hint:SETTING_HINT_AUTHENTICATE_SECRET],
// asr
[SettingItem buildGroup:SETTING_ASR val:@"" hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_RESOURCE_DOWNLOAD val:false hint:@""],
[SettingItem buildString:SETTING_ASR_MODEL_NAME val:SDEF_ASR_DEFAULT_MODEL_NAME hint:@""],
[SettingItem buildBool:SETTING_ASR_RECORDER_SAVE val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_ITN val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_SHOW_LANGUAGE val:false hint:@""],
[SettingItem buildOptions:SETTING_ASR_RESULT_TYPE val:[SettingOptions build:SETTING_OPTIONS_ASR_RESULT_TYPE() choose:0] hint:@""],
]];
// Capt settings
sharedInstance.captSettings = [Settings build];
[sharedInstance.captSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_CLUSTER val:SDEF_CAPT_DEFAULT_CLUSTER hint:SETTING_HINT_CLUSTER],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:@"" hint:SETTING_HINT_URI],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_APPID hint:@""],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_ENABLE_AEC val:false hint:@""],
[SettingItem buildBool:SETTING_RESTART_AUDIO_SESSION_ENABLE val:false hint:@""],
[SettingItem buildBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE val:false hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_SPEECH_DURATION val:15000 hint:@""],
// capt
[SettingItem buildGroup:SETTING_CAPT val:@"" hint:@""],
[SettingItem buildBool:SETTING_CAPT_RECORDER_SAVE val:false hint:@""],
[SettingItem buildBool:SETTING_CAPT_STREAMING_MODE val:false hint:@""],
[SettingItem buildOptions:SETTING_CAPT_CORE_TYPE val:[SettingOptions build:SETTING_OPTIONS_CAPT_CORE_TYPE() choose:0] hint:@""],
[SettingItem buildInt:SETTING_CAPT_DIFFICULTY_LEVEL val:2 hint:SETTING_HINT_DIFFICULTY_LEVEL],
]];
// Fulllink settings
sharedInstance.fulllinkSettings = [Settings build];
[sharedInstance.fulllinkSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_CLUSTER val:@"" hint:SETTING_HINT_CLUSTER],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildInt:SETTING_SAMPLE_RATE val:16000 hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_ENABLE_AEC val:false hint:@""],
[SettingItem buildBool:SETTING_RESTART_AUDIO_SESSION_ENABLE val:false hint:@""],
[SettingItem buildBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE val:false hint:@""],
// fulllink
[SettingItem buildGroup:SETTING_FULLLINK val:@"" hint:@""],
[SettingItem buildOptions:SETTING_FULLLINK_ENGINE_TYPE val:[SettingOptions build:SETTING_OPTIONS_FULLLINK_ENGINE_TYPE() choose:0] hint:@""],
[SettingItem buildOptions:SETTING_FULLLINK_WAKEUP_WORDS val:[SettingOptions build:SETTING_OPTIONS_FULLLINK_WAKEUP_WORDS() choose:0] hint:@""],
[SettingItem buildBool:SETTING_FULLLINK_ENABLE_RECORDER_DUMP val:false hint:@""],
[SettingItem buildBool:SETTING_FULLLINK_ENABLE_KWS_DUMP val:false hint:@""],
[SettingItem buildOptions:SETTING_FULLLINK_WAKEUP_MODE val:[SettingOptions build:SETTING_OPTIONS_FULLLINK_WAKEUP_MODE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_FULLLINK_ONLY_ASR val:false hint:@""],
[SettingItem buildBool:SETTING_FULLLINK_DISABLE_TTS val:false hint:@""],
[SettingItem buildBool:SETTING_FULLLINK_DISABLE_SIGNAL val:false hint:@""],
[SettingItem buildInt:SETTING_FULLLINK_DISABLE_SIGTHREAD_PRI val:-10 hint:@""],
[SettingItem buildString:SETTING_FULLLINK_DISABLE_FILE_OR_DIRECTORY_NAME val:@"" hint:@""],
]];
// Tts settings
sharedInstance.ttsSettings = [Settings build];
[sharedInstance.ttsSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_TOKEN hint:@""],
[SettingItem buildString:SETTING_CLUSTER val:SDEF_TTS_DEFAULT_CLUSTER hint:SETTING_HINT_CLUSTER],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_TTS_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildString:SETTING_ONLINE_VOICE val:SDEF_TTS_DEFAULT_ONLINE_VOICE hint:SETTING_HINT_ONLINE_VOICE],
[SettingItem buildString:SETTING_ONLINE_VOICE_TYPE val:SDEF_TTS_DEFAULT_ONLINE_VOICE_TYPE hint:SETTING_HINT_ONLINE_VOICE_TYPE],
[SettingItem buildString:SETTING_OFFLINE_VOICE val:SDEF_TTS_DEFAULT_OFFLINE_VOICE hint:SETTING_HINT_OFFLINE_VOICE],
[SettingItem buildString:SETTING_OFFLINE_VOICE_TYPE val:SDEF_TTS_DEFAULT_OFFLINE_VOICE_TYPE hint:SETTING_HINT_OFFLINE_VOICE_TYPE],
[SettingItem buildOptions:SETTING_AUTHENTICATION_TYPE val:[SettingOptions build:SETTING_OPTIONS_AUTHENTICATION_TYPE() choose:0] hint:@""],
[SettingItem buildString:SETTING_LICENSE_NAME val:SDEF_LICENSE_NAME hint:SETTING_HINT_LICENSE_NAME],
[SettingItem buildString:SETTING_LICENSE_BUSI_ID val:SDEF_LICENSE_BUSI_ID hint:SETTING_HINT_LICENSE_BUSI_ID],
[SettingItem buildString:SETTING_BUSINESS_KEY val:SDEF_BUSINESS_KEY hint:SETTING_HINT_BUSINESS_KEY],
[SettingItem buildString:SETTING_AUTHENTICATE_SECRET val:SDEF_SECRET hint:SETTING_HINT_AUTHENTICATE_SECRET],
[SettingItem buildBool:SETTING_DISABLE_WS_RECONNECT val:false hint:@""],
[SettingItem buildInt:SETTING_AUDIO_FADEOUT_DURATION val:0 hint:@""],
// tts
[SettingItem buildGroup:SETTING_TTS val:@"" hint:@""],
[SettingItem buildOptions:SETTING_TTS_WORK_MODE val:[SettingOptions build:SETTING_OPTIONS_TTS_WORK_MODE() choose:0] hint:@""],
[SettingItem buildOptions:SETTING_TTS_TEXT_TYPE val:[SettingOptions build:SETTING_OPTIONS_TTS_TEXT_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_PREVENT_PLAYER_CREATION val:false hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_RESUME_FROM_BREAKPOINT val:true hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_PLAYER val:true hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_DUMP val:false hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_DATA_CALLBACK val:false hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_WORD_LEVEL_PROGRESS_UPDATE val:true hint:@""],
[SettingItem buildInt:SETTING_TTS_SILENCE_DURATION val:0 hint:@""],
[SettingItem buildDouble:SETTING_TTS_SPEAK_SPEED val:1.0 hint:@""],
[SettingItem buildDouble:SETTING_TTS_AUDIO_VOLUME val:1.0 hint:@""],
[SettingItem buildDouble:SETTING_TTS_AUDIO_PITCH val:1.0 hint:@""],
[SettingItem buildInt:SETTING_TTS_SAMPLE_RATE val:16000 hint:SETTING_HINT_TTS_SAMPLE_RATE],
[SettingItem buildBool:SETTING_TTS_ENABLE_CACHE val:false hint:@""],
[SettingItem buildBool:SETTING_TTS_WITH_INTENT val:false hint:@""],
[SettingItem buildString:SETTING_TTS_ONLINE_LANGUAGE val:SDEF_TTS_DEFAULT_ONLINE_LANGUAGE hint:SETTING_HINT_TTS_ONLINE_LANGUAGE],
[SettingItem buildString:SETTING_TTS_EMOTION val:@"" hint:SETTING_HINT_TTS_EMOTION],
[SettingItem buildBool:SETTING_TTS_USE_VOICECLONE_VOICE val:false hint:@""],
[SettingItem buildString:SETTING_TTS_BACKEND_CLUSTER val:@"demo_test" hint:SETTING_HINT_BACKEND_CLUSTER],
[SettingItem buildString:SETTING_TTS_REQUEST_ID val:@"" hint:@""],
[SettingItem buildString:SETTING_TTS_REQUEST_PARAMS val:@"" hint:@""],
[SettingItem buildOptions:SETTING_TTS_OFFLINE_RESOURCE_FORMAT val:[SettingOptions build:SETTING_OPTIONS_TTS_OFFLINE_RESOURCE_FORMAT() choose:0] hint:@""],
[SettingItem buildString:SETTING_TTS_OFFLINE_LANGUAGE val:SDEF_TTS_DEFAULT_OFFLINE_LANGUAGE hint:SETTING_HINT_TTS_OFFLINE_LANGUAGE],
[SettingItem buildString:SETTING_TTS_MODEL_NAME val:@"aispeech_tts" hint:SETTING_HINT_TTS_MODEL_NAME],
[SettingItem buildBool:SETTING_TTS_LIMIT_CPU_USAGE val:false hint:@""],
]];
// VoiceClone settings
sharedInstance.voiceCloneSettings = [Settings build];
[sharedInstance.voiceCloneSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildInt:SETTING_SAMPLE_RATE val:44100 hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_APPID hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
// voiceclone
[SettingItem buildGroup:SETTING_VOICECLONE val:@"" hint:@""],
[SettingItem buildString:SETTING_VOICECLONE_ADDRESS val:SDEF_DEFAULT_HTTP_ADDRESS hint:@""],
[SettingItem buildString:SETTING_VOICECLONE_STREAM_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:@""],
[SettingItem buildString:SETTING_VOICECLONE_UID val:SDEF_UID hint:SETTING_HINT_VOICECLONE_UID],
[SettingItem buildString:SETTING_VOICECLONE_QUERY_UIDS val:SDEF_VOICECLONE_DEFAULT_UIDS hint:SETTING_HINT_VOICECLONE_QUERY_UIDS],
[SettingItem buildString:SETTING_VOICECLONE_VOICE_TYPE val:@"" hint:SETTING_HINT_VOICECLONE_VOICE_TYPE],
[SettingItem buildBool:SETTING_VOICECLONE_GENDER val:false hint:@""],
[SettingItem buildInt:SETTING_VOICECLONE_TASKID val:SDEF_VOICECLONE_DEFAULT_TASK_ID hint:@""],
]];
// VoiceConv settings
sharedInstance.voiceConvSettings = [Settings build];
[sharedInstance.voiceConvSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_VOICE val:SDEF_VOICECONV_DEFAULT_VOICE hint:SETTING_HINT_VOICE],
[SettingItem buildString:SETTING_VOICE_TYPE val:SDEF_VOICECONV_DEFAULT_VOICE_TYPE hint:SETTING_HINT_VOICE_TYPE],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
// voiceconv
[SettingItem buildGroup:SETTING_VOICECONV val:@"" hint:@""],
[SettingItem buildInt:SETTING_VOICECONV_RESULT_SAMPLE_RATE val:24000 hint:@""],
[SettingItem buildBool:SETTING_VOICECONV_ENABLE_RECORD_DUMP val:true hint:@""],
[SettingItem buildBool:SETTING_VOICECONV_ENABLE_RESULT_DUMP val:true hint:@""],
[SettingItem buildInt:SETTING_VOICECONV_REQUEST_INTERVAL val:200 hint:@""],
]];
// Dialog settings
sharedInstance.dialogSettings = [Settings build];
[sharedInstance.dialogSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_APPKEY val:SDEF_APPKEY hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_TOKEN hint:@""],
[SettingItem buildString:SETTING_RESOURCE_ID val:SDEF_DIALOG_DEFAULT_RESOURCE_ID hint:@""],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_DIALOG_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildString:SETTING_REQUEST_HEADERS val:@"" hint:@""],
// dialog
[SettingItem buildGroup:SETTING_DIALOG val:@"" hint:@""],
[SettingItem buildBool:SETTING_DIALOG_ENABLE_RECORDER_DUMP val:false hint:@""],
[SettingItem buildBool:SETTING_DIALOG_ENABLE_PLAYER_DUMP val:false hint:@""],
[SettingItem buildString:SETTING_DIALOG_BOT_NAME val:@"豆包" hint:@""],
]];
// Dialog settings
sharedInstance.dialogDelegateSettings = [Settings build];
[sharedInstance.dialogDelegateSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_APPKEY val:SDEF_APPKEY hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_TOKEN hint:@""],
[SettingItem buildString:SETTING_RESOURCE_ID val:SDEF_DIALOG_DEFAULT_RESOURCE_ID hint:@""],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_DIALOG_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildString:SETTING_REQUEST_HEADERS val:@"" hint:@""],
// dialog
[SettingItem buildGroup:SETTING_DIALOG val:@"" hint:@""],
[SettingItem buildBool:SETTING_DIALOG_ENABLE_RECORDER_DUMP val:false hint:@""],
[SettingItem buildBool:SETTING_DIALOG_ENABLE_PLAYER_DUMP val:false hint:@""],
[SettingItem buildString:SETTING_DIALOG_BOT_NAME val:@"豆包" hint:@""],
]];
// BiTTS settings
sharedInstance.bittsSettings = [Settings build];
[sharedInstance.bittsSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_BITTS_DEFAULT_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_BITTS_DEFAULT_TOKEN hint:@""],
[SettingItem buildString:SETTING_RESOURCE_ID val:SDEF_BITTS_DEFAULT_RESOURCE_ID hint:@""],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_BITTS_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildString:SETTING_REQUEST_HEADERS val:@"{}" hint:@""],
// bitts
[SettingItem buildGroup:SETTING_BITTS val:@"" hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_PLAYER val:true hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_DUMP val:false hint:@""],
[SettingItem buildBool:SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK val:true hint:@""],
]];
// UniTTS settings
sharedInstance.uniTtsSettings = [Settings build];
[sharedInstance.uniTtsSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_UNITTS_DEFAULT_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_UNITTS_DEFAULT_TOKEN hint:@""],
[SettingItem buildString:SETTING_RESOURCE_ID val:SDEF_UNITTS_DEFAULT_RESOURCE_ID hint:@""],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_UNITTS_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildString:SETTING_REQUEST_HEADERS val:@"{}" hint:@""],
// unitts
[SettingItem buildGroup:SETTING_UNITTS val:@"" hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_PLAYER val:true hint:@""],
[SettingItem buildBool:SETTING_TTS_ENABLE_DUMP val:false hint:@""],
[SettingItem buildBool:SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK val:true hint:@""],
]];
// Vad settings
sharedInstance.vadSettings = [Settings build];
[sharedInstance.vadSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_RESTART_AUDIO_SESSION_ENABLE val:false hint:@""],
[SettingItem buildBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE val:false hint:@""],
// vad
[SettingItem buildGroup:SETTING_VAD val:@"" hint:@""],
[SettingItem buildBool:SETTING_VAD_RECORDER_SAVE val:false hint:@""],
[SettingItem buildInt:SETTING_VAD_HEAD_SILENCE_THRESHOLD val:4000 hint:@""],
[SettingItem buildInt:SETTING_VAD_TAIL_SILENCE_THRESHOLD val:2000 hint:@""],
]];
// Au settings
sharedInstance.auSettings = [Settings build];
[sharedInstance.auSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_AU_DEFAULT_APP_ID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_TOKEN hint:@""],
[SettingItem buildString:SETTING_CLUSTER val:SDEF_AU_DEFAULT_CLUSTER hint:SETTING_HINT_CLUSTER],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_AU_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_AU_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
// au
[SettingItem buildGroup:SETTING_AU val:@"" hint:@""],
[SettingItem buildOptions:SETTING_AU_ABILITY val:[SettingOptions build:SETTING_OPTIONS_AU_ABILITY() choose:2] hint:@""],
[SettingItem buildBool:SETTING_AU_RECORDER_SAVE val:false hint:@""],
[SettingItem buildInt:SETTING_AU_PROCESS_TIMEOUT val:3000 hint:@""],
[SettingItem buildInt:SETTING_AU_AUDIO_PACKET_DURATION val:80 hint:@""],
[SettingItem buildInt:SETTING_AU_EMPTY_PACKET_INTERVAL val:500 hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_SPEECH_DURATION val:60000 hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_MUSIC_DURATION val:12000 hint:@""],
// asr
[SettingItem buildGroup:SETTING_ASR val:@"" hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_DDC val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_ITN val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_NLU_PUNC val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_SHOW_LANGUAGE val:false hint:@""],
[SettingItem buildString:SETTING_ASR_LANGUAGE val:@"en-US" hint:@""],
[SettingItem buildString:SETTING_ASR_HOTWORDS val:@"{\"hotwords\":[{\"word\":\"\",\"scale\":\"2.0\"}]}" hint:@""],
[SettingItem buildInt:SETTING_ASR_VAD_START_SILENCE_TIME val:0 hint:@""],
[SettingItem buildInt:SETTING_ASR_VAD_END_SILENCE_TIME val:0 hint:@""],
[SettingItem buildString:SETTING_ASR_VAD_MODE val:@"" hint:@""],
[SettingItem buildOptions:SETTING_ASR_RESULT_TYPE val:[SettingOptions build:SETTING_OPTIONS_ASR_RESULT_TYPE() choose:0] hint:@""],
]];
// Afp
sharedInstance.afpSettings = [Settings build];
[sharedInstance.afpSettings registerItems:@[
// afp
[SettingItem buildGroup:SETTING_AFP val:@"" hint:@""],
[SettingItem buildOptions:SETTING_MUSIC_ENGINE_NAME val:[SettingOptions build:SETTING_OPTIONS_MUSIC_ENGINE_NAME() choose:0] hint:@""],
[SettingItem buildOptions:SETTING_AFP_RESULT_TYPE val:[SettingOptions build:SETTING_OPTIONS_AFP_RESULT_TYPE() choose:0] hint:@""],
]];
// Kws settings
sharedInstance.kwsSettings = [Settings build];
[sharedInstance.kwsSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
// kws
[SettingItem buildGroup:SETTING_KWS val:@"" hint:@""],
[SettingItem buildString:SETTING_KWS_MODEL_NAME val:@"aispeech_kws_douyin" hint:@""],
[SettingItem buildString:SETTING_KWS_CUSTOM_WORDS val:@"{\"word_list\":[{\"name\":\"\",\"keyword_type\":0,\"min_dur\":0.15,\"max_dur\":3,\"threshold\":-3.6}]}" hint:@""],
]];
// Test Afp
sharedInstance.testAfpSettings = [Settings build];
[sharedInstance.testAfpSettings registerItems:@[
// afp
[SettingItem buildGroup:SETTING_AFP val:@"" hint:@""],
[SettingItem buildOptions:SETTING_AFP_RESULT_TYPE val:[SettingOptions build:SETTING_OPTIONS_AFP_RESULT_TYPE() choose:0] hint:@""],
[SettingItem buildInt:SETTING_AFP_INSTANCE_NUMBER val:10 hint:@""],
]];
// Test: asr offline rtf settins
sharedInstance.testAsrOfflineRtfSettings = [Settings build];
[sharedInstance.testAsrOfflineRtfSettings registerItems:@[
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildInt:SETTING_STREAM_PACKAGE_DURATION val:20000 hint:@""],
// asr
[SettingItem buildGroup:SETTING_ASR val:@"" hint:@""],
[SettingItem buildBool:SETTING_ASR_RECORDER_SAVE val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_ITN val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_SHOW_LANGUAGE val:false hint:@""],
]];
// Test: asr stress settings
sharedInstance.testAsrStressSettings = [Settings build];
[sharedInstance.testAsrStressSettings registerItems:@[
// stress
[SettingItem buildGroup:SETTING_ASR_STRESS val:@"" hint:@""],
[SettingItem buildOptions:SETTING_ASR_STRESS_SCENEID val:[SettingOptions build:SETTING_OPTIONS_ASR_STRESS_TYPE() choose:0] hint:@""],
// common
[SettingItem buildGroup:SETTING_COMMON val:@"" hint:@""],
[SettingItem buildString:SETTING_APPID val:SDEF_APPID hint:@""],
[SettingItem buildString:SETTING_TOKEN val:SDEF_TOKEN hint:@""],
[SettingItem buildString:SETTING_CLUSTER val:SDEF_ASR_DEFAULT_CLUSTER hint:SETTING_HINT_CLUSTER],
[SettingItem buildString:SETTING_ADDRESS val:SDEF_DEFAULT_ADDRESS hint:SETTING_HINT_ADDRESS],
[SettingItem buildString:SETTING_URI val:SDEF_ASR_DEFAULT_URI hint:SETTING_HINT_URI],
[SettingItem buildBool:SETTING_GET_VOLUME val:false hint:@""],
[SettingItem buildOptions:SETTING_RECORD_TYPE val:[SettingOptions build:SETTING_OPTIONS_RECORD_TYPE() choose:0] hint:@""],
[SettingItem buildBool:SETTING_RESTART_AUDIO_SESSION_ENABLE val:false hint:@""],
[SettingItem buildBool:SETTING_RESUME_OTHERS_INTERRUPTED_PLAYBACK_ENABLE val:false hint:@""],
[SettingItem buildInt:SETTING_VAD_MAX_SPEECH_DURATION val:15000 hint:@""],
[SettingItem buildInt:SETTING_STREAM_PACKAGE_DURATION val:20000 hint:@""],
// asr
[SettingItem buildGroup:SETTING_ASR val:@"" hint:@""],
[SettingItem buildBool:SETTING_ASR_RECORDER_SAVE val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_DDC val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_ITN val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_ENABLE_NLU_PUNC val:true hint:@""],
[SettingItem buildBool:SETTING_ASR_KEEP_RECORDING val:false hint:@""],
[SettingItem buildBool:SETTING_ASR_SHOW_LANGUAGE val:false hint:@""],
[SettingItem buildString:SETTING_ASR_LANGUAGE val:@"en-US" hint:@""],
[SettingItem buildString:SETTING_ASR_HOTWORDS val:@"{\"hotwords\":[{\"word\":\"\",\"scale\":\"2.0\"}]}" hint:@""],
[SettingItem buildInt:SETTING_ASR_VAD_START_SILENCE_TIME val:0 hint:@""],
[SettingItem buildInt:SETTING_ASR_VAD_END_SILENCE_TIME val:0 hint:@""],
[SettingItem buildString:SETTING_ASR_VAD_MODE val:@"" hint:@""],
[SettingItem buildOptions:SETTING_ASR_RESULT_TYPE val:[SettingOptions build:SETTING_OPTIONS_ASR_RESULT_TYPE() choose:0] hint:@""],
]];
});
return sharedInstance;
}
- (Settings*)getSettings:(NSString*)viewId {
if ([viewId isEqualToString: VIEW_ASR]) {
return self.asrSettings;
} else if ([viewId isEqualToString: VIEW_BIGASR]) {
return self.bigAsrSettings;
} else if ([viewId isEqualToString: VIEW_ASR_OFFLINE]) {
return self.asrOfflineSettings;
} else if ([viewId isEqualToString: VIEW_CAPT]) {
return self.captSettings;
} else if ([viewId isEqualToString: VIEW_FULLLINK]) {
return self.fulllinkSettings;
} else if ([viewId isEqualToString: VIEW_TTS]) {
return self.ttsSettings;
} else if ([viewId isEqualToString: VIEW_VOICECLONE]) {
return self.voiceCloneSettings;
} else if ([viewId isEqualToString: VIEW_VOICECONV]) {
return self.voiceConvSettings;
} else if ([viewId isEqualToString: VIEW_DIALOG]) {
return self.dialogSettings;
} else if ([viewId isEqualToString: VIEW_DIALOG_DELEGATE]) {
return self.dialogDelegateSettings;
} else if ([viewId isEqualToString: VIEW_BITTS]) {
return self.bittsSettings;
} else if ([viewId isEqualToString: VIEW_UNITTS]) {
return self.uniTtsSettings;
} else if ([viewId isEqualToString: VIEW_VAD]) {
return self.vadSettings;
} else if ([viewId isEqualToString: VIEW_AU]) {
return self.auSettings;
} else if ([viewId isEqualToString: VIEW_AFP]) {
return self.afpSettings;
} else if ([viewId isEqualToString: VIEW_KWS]) {
return self.kwsSettings;
} else if ([viewId isEqualToString: VIEW_TEST_AFP]) {
return self.testAfpSettings;
} else if ([viewId isEqualToString: VIEW_TEST_ASR_OFFLINE_RTF]) {
return self.testAsrOfflineRtfSettings;
} else if ([viewId isEqualToString: VIEW_TEST_ASR_STRESS]) {
return self.testAsrStressSettings;
} else {
NSLog( @"View id %@ is not found yet!", viewId);
}
return [Settings build];
}
@end

View File

@ -0,0 +1,19 @@
//
// SettingsViewController.h
// SpeechDemo
//
// Created by fangweiwei on 2020/2/28.
// Copyright © 2020 fangweiwei. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface SettingsViewController : UITableViewController
@property (strong, nonatomic) NSString *viewId;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,42 @@
//
// SettingsViewController.m
// SpeechDemo
//
// Created by fangweiwei on 2020/2/28.
// Copyright © 2020 fangweiwei. All rights reserved.
//
#import "SettingsViewController.h"
#import "SettingsHelper.h"
#import "SettingViewDelegate.h"
@interface SettingsViewController () <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITableView *settingTableView;
@property (strong, nonatomic) SettingViewDelegate *settingViewDelegate;
@end
@implementation SettingsViewController
- (void)viewDidLoad {
[super viewDidLoad];
SettingsHelper* settingsInstance = [SettingsHelper shareInstance];
self.settingViewDelegate = [SettingViewDelegate build:[settingsInstance getSettings:self.viewId]];
self.settingTableView.delegate = self.settingViewDelegate;
self.settingTableView.dataSource = self.settingViewDelegate;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
[segue destinationViewController];
}
@end

View File

@ -0,0 +1,17 @@
//
// TestAfpViewController.h
// SpeechDemo
//
// Created by bytedance on 2024/7/10.
// Copyright © 2024 chengzihao.ds. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestAfpViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,265 @@
//
// TestAfpViewController.m
// SpeechDemo
//
// Created by chengzihao.ds on 2024/7/10.
// Copyright © 2024 chengzihao.ds. All rights reserved.
//
#import "TestAfpViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
@interface AfpPressureTask : NSObject
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, strong) SpeechEngine *engine;
@property (nonatomic, weak) NSString *debugPath;
@property (nonatomic, assign) int resultType;
@property (nonatomic, assign) int taskId;
@property (nonatomic, assign) BOOL running;
- (instancetype)initWithTaskId:(int)taskId;
- (void)startTask;
- (void)stopTask;
- (void)taskLoop;
- (int)fetchResult;
@end
@implementation AfpPressureTask
- (instancetype)initWithTaskId:(int)taskId {
self = [super init];
if (self) {
_taskId = taskId;
_running = YES;
_engine = NULL;
_debugPath = NULL;
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(taskLoop) object:nil];
}
return self;
}
- (void)startTask {
[self.thread start];
}
- (void)stopTask {
self.running = NO;
}
- (void)taskLoop {
@autoreleasepool {
NSString *path = [self.debugPath stringByAppendingPathComponent:@"test_afp.pcm"];
while (self.running) {
@autoreleasepool {
// Init
self.engine = [[SpeechEngine alloc] init];
[self.engine createEngineWithDelegate:nil];
[self.engine setStringParam:SE_AFP_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
SEEngineErrorCode ret = [self.engine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %d", ret);
}
// Process
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error];
if (data) {
// Read success.
ret = [self.engine ProcessAudio:(int16_t *)data.bytes length:(int32_t)(data.length / 2) isFinal:TRUE];
if (ret != SENoError) {
NSLog(@"Process Audio failed: %d", ret);
}
}
// FetchResult
ret = [self fetchResult];
if (ret != SENoError) {
NSLog(@"Fetch Result failed: %d", ret);
}
// Reset
ret = [self.engine ResetEngine];
if (ret != SENoError) {
NSLog(@"Reset Engine failed: %d", ret);
}
// Destroy
[self.engine destroyEngine];
self.engine = nil;
}
}
NSLog(@"Task %d has stopped", self.taskId);
}
}
- (int) fetchResult {
@autoreleasepool {
NSString *filename;
if (self.resultType == SEAfpResult) {
filename = [NSString stringWithFormat:@"test_afp_out_%d.bytes", self.taskId];
} else {
filename = [NSString stringWithFormat:@"test_afp_out_%d.json", self.taskId];
}
if (self.resultType == SEAfpResult) {
NSData *result;
SEEngineErrorCode ret = [self.engine FetchResult:&result];
if (ret != SENoError) {
NSLog(@"Fetch Result failed: %d", ret);
return ret;
}
NSFileHandle *file = [FileUtils openFileForWriting:filename inPath:self.debugPath];
[FileUtils writeData:result toFileHandel:file];
[FileUtils closeFile:file];
} else if (self.resultType == SEAfpSliceResult) {
NSString* result = [self.engine FetchStringResult:SEAfpSliceResult];
// err_code
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:[result dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];
int errCode = [jsonResult[@"err_code"] intValue];
if (errCode != SENoError) {
NSLog(@"Fetch Result failed: %d", errCode);
return errCode;
}
NSFileHandle *file = [FileUtils openFileForWriting:filename inPath:self.debugPath];
[FileUtils writeString:result toFileHandel:file];
[FileUtils closeFile:file];
}
}
return 0;
}
@end
@interface TestAfpViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (strong, nonatomic) NSMutableArray<AfpPressureTask *> *tasks;
@property (nonatomic, strong) NSString *deviceID;
@property (strong, nonatomic) NSString *debugPath;
// Settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation TestAfpViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_TEST_AFP];
}
- (void)viewDidDisappear:(BOOL)animated {
[self stopPressureTest];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - UI Actions
- (IBAction)startStopPressureTest:(id)sender {
if (self.tasks == NULL) {
[self startPressureTest];
[self.resultTextView setText: [NSString stringWithFormat:@"压测开始: %d个实例", [self.settings getInt: SETTING_AFP_INSTANCE_NUMBER]]];
} else {
[self stopPressureTest];
[self.resultTextView setText: @"压测结束"];
}
}
#pragma mark - Init Methods
- (void)startPressureTest {
if (self.tasks != NULL) {
return;
}
self.tasks = [NSMutableArray array];
for (int i = 0; i < [self.settings getInt: SETTING_AFP_INSTANCE_NUMBER]; i++) {
AfpPressureTask *task = [[AfpPressureTask alloc] initWithTaskId:i];
task.resultType = [self getResultType];
task.debugPath = self.debugPath;
[self.tasks addObject:task];
[task startTask];
}
}
- (void)stopPressureTest {
if (self.tasks == NULL) {
return;
}
//
for (AfpPressureTask *task in self.tasks) {
[task stopTask];
}
//
for (AfpPressureTask *task in self.tasks) {
while (![task.thread isFinished]) {
[NSThread sleepForTimeInterval:0.1];
}
}
self.tasks = NULL;
}
#pragma mark - Helper
- (SEResultType)getResultType {
SettingOptions* resultTypeOptions = [self.settings getOptions:SETTING_AFP_RESULT_TYPE];
switch (resultTypeOptions.chooseIdx) {
case 0:
return SEAfpResult;
case 1:
default:
return SEAfpSliceResult;
}
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_TEST_AFP forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// TestAsrOfflineRtfViewController.h
// SpeechDemo
//
// Created by bytedance on 2023/6/6.
// Copyright © 2023 chengzihao.ds. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TestAsrOfflineRtfViewController : UIViewController
@end

View File

@ -0,0 +1,339 @@
//
// TestAsrOfflineRtfViewController.m
// SpeechDemo
//
// Created by bytedance on 2023/6/6.
// Copyright © 2023 chengzihao.ds. All rights reserved.
//
#import "TestAsrOfflineRtfViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
@interface TestAsrOfflineRtfViewController () <SpeechEngineDelegate, UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineInitButton;
@property (weak, nonatomic) IBOutlet UIButton *engineUninitButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (strong, nonatomic) SpeechEngine *curEngine;
@property (assign, nonatomic) BOOL engineStarted;
@property (nonatomic, strong) NSString *deviceID;
@property (nonatomic, assign) long feedTimestamp;
@property (nonatomic, assign) int audioLength;
@property (strong, nonatomic) NSString *debugPath;
// settings
@property (strong, nonatomic) Settings *settings;
@end
@implementation TestAsrOfflineRtfViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_TEST_ASR_OFFLINE_RTF];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
[self.statusTextView setText:@"Waiting for init."];
[self decorateTextView:self.resultTextView];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.engineStarted = FALSE;
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - SpeechEngineDelegate
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
[self speechEngineStarted];
break;
case SEEngineStop:
[self speechEngineStopped];
break;
case SEEngineError:
[self speechEngineError:data];
break;
case SEAsrPartialResult:
[self speechEngineResult:data isFinal:FALSE];
break;
case SEFinalResult:
[self speechEngineResult:data isFinal:TRUE];
break;
case SEVolumeLevel:
NSLog(@"volume level: %s", (char*)data.bytes);
break;
case SEEngineLog:
NSLog(@"engine log: %s", (char*)data.bytes);
break;
default:
break;
}
}
#pragma mark - UI Actions
- (IBAction)initEngine:(id)sender {
[self initEngine];
}
- (IBAction)uninitEngine:(id)sender {
if (self.engineStarted) {
[self.statusTextView setText:@"Engine is busy, stop it first!"];
return;
}
[self uninitEngine];
[self.resultTextView setTextColor:UIColor.grayColor];
[self.resultTextView setText:@"点击或按住说话后,展示语音识别结果"];
}
- (IBAction)startEngine:(id)sender {
NSLog(@"Start engine.");
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
[self.curEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
return;
}
}
- (IBAction)stopEngine:(id)sender {
NSLog(@"Stop engine.");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
#pragma mark - Init Methods
- (void)initEngine {
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
if (appDelegate.deviceID.length < 1) {
self.engineInitButton.enabled = FALSE;
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Waiting for get deviceID."];
sleep(1);
[self initEngine];
});
return;
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
}
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"Create speech engine failed.");
return;
}
[self.resultTextView setTextColor:UIColor.blackColor];
NSLog(@"Engine version: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"Debug path: %@", self.debugPath);
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
[self.curEngine setStringParam:[self.settings getString:SETTING_APPID] forKey:SE_PARAMS_KEY_APP_ID_STRING];
[self.curEngine setStringParam:@"388808087185088" forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setIntParam:1 forKey:SE_PARAMS_KEY_CHANNEL_NUM_INT];
[self.curEngine setBoolParam:FALSE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
[self.curEngine setStringParam:@"" forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
if ([self.settings getBool:SETTING_ASR_RECORDER_SAVE]) {
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
}
[self.curEngine setStringParam:SE_RECORDER_TYPE_STREAM forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
[self.curEngine setStringParam:SE_ASR_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
[self.curEngine setIntParam:16000 forKey:SE_PARAMS_KEY_SAMPLE_RATE_INT];
[self.curEngine setBoolParam:true forKey:SE_PARAMS_KEY_ASR_SHOW_UTTER_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_SHOW_LANGUAGE] forKey:SE_PARAMS_KEY_ASR_SHOW_LANG_BOOL];
[self.curEngine setBoolParam:true forKey:SE_PARAMS_KEY_ASR_SHOW_VOLUME_BOOL];
[self.curEngine setIntParam:SEAsrWorkModeOfflineFlute forKey:SE_PARAMS_KEY_ASR_WORK_MODE_INT];
NSString* modelsPath = [NSString stringWithFormat:@"%@/models", self.debugPath];
[self.curEngine setStringParam:modelsPath forKey:SE_PARAMS_KEY_ASR_OFF_RESOURCE_PATH_STRING];
NSLog(@"Models path: %@", modelsPath);
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret != SENoError) {
NSLog(@"Init Engine failed: %ld", ret);
}
if (ret == SENoError) {
[self speechEngineInitOk];
} else {
[self speechEngineInitFailed];
}
}
- (void)uninitEngine {
[self.curEngine destroyEngine];
self.curEngine = nil;
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = FALSE;
}
- (void)setHotWords:(NSString*) hotWords {
[self.curEngine sendDirective:SEDirectiveUpdateAsrHotWords data: hotWords];
}
#pragma mark - Engine Callback
- (void)speechEngineNoPermission {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"No permission!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineInitOk {
dispatch_async(dispatch_get_main_queue(), ^{
[self.statusTextView setText:@"Ready"];
[self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@", self.deviceID]];
self.engineUninitButton.enabled = TRUE;
self.engineInitButton.enabled = FALSE;
self.startEngineButton.enabled = TRUE;
});
}
- (void)speechEngineInitFailed {
dispatch_async(dispatch_get_main_queue(), ^{
[self uninitEngine];
[self.statusTextView setText:@"Failed to init engine!"];
self.engineInitButton.enabled = TRUE;
self.engineUninitButton.enabled = FALSE;
});
}
- (void)speechEngineStarted {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = true;
[self.statusTextView setText:@"Engine Started!"];
self.startEngineButton.enabled = FALSE;
self.stopEngineButton.enabled = TRUE;
// Read whole file into nsdata.
NSString* filePath = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"asr_rec_file.pcm"];
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
if (!fileData) {
[self.resultTextView setText:@"ERROR: File asr_rec_file.pcm not found!"];
[self stopEngine:NULL];
return;
}
int maxLength = [self.settings getInt:SETTING_STREAM_PACKAGE_DURATION] * 16 * 2;
int16_t* feedData = (int16_t*) malloc(maxLength * sizeof(int16_t));
self.audioLength = MIN(maxLength, [fileData length]);
memcpy(feedData, [fileData bytes], self.audioLength);
self.feedTimestamp = [[NSDate date] timeIntervalSince1970] * 1000;
[self.curEngine feedAudio:feedData length:self.audioLength / 2];
[self.curEngine sendDirective:SEDirectiveFinishTalking];
free(feedData);
feedData = NULL;
});
}
- (void)speechEngineStopped {
dispatch_async(dispatch_get_main_queue(), ^{
self.engineStarted = FALSE;
[self.statusTextView setText:@"Engine Stopped!"];
self.startEngineButton.enabled = TRUE;
self.stopEngineButton.enabled = FALSE;
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
dispatch_async(dispatch_get_main_queue(), ^{
long delay = 0;
if (isFinal) {
long current = [[NSDate date] timeIntervalSince1970] * 1000;
delay = current - self.feedTimestamp;
}
NSError *error;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
NSMutableString *text = [[NSMutableString alloc] initWithString:@""];
if (![jsonResult objectForKey:@"result"]) {
return;
}
[text appendFormat:@"result: %@", [[[jsonResult objectForKey:@"result"] firstObject] objectForKey:@"text"]];
if (isFinal) {
[text appendFormat:@"\naudio_length: %ld", self.audioLength];
[text appendFormat:@"\nresponse_delay: %ld", delay];
[text appendFormat:@"\nrtf: %f", (float) delay / (self.audioLength / 16 / 2)];
}
if (text.length) {
[self.resultTextView setText:[text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
NSLog(@"asr test result: %@", text);
}
});
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
});
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_TEST_ASR_OFFLINE_RTF forKey:@"viewId"];
}
@end

View File

@ -0,0 +1,13 @@
//
// AsrViewController.h
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TestAsrViewController : UIViewController
@end

View File

@ -0,0 +1,519 @@
//
// TestAsrViewController.m
// SpeechDemo
//
// Created by bytedance on 2020/9/8.
// Copyright © 2020 fengkai.0518. All rights reserved.
//
#import "TestAsrViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"
@interface TestAsrViewController () <SpeechEngineDelegate, UITextViewDelegate>
// UI
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *startButton;
@property (weak, nonatomic) IBOutlet UIButton *stopButton;
// Device ID: 线
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: SDK
@property (strong, nonatomic) NSString *debugPath;
// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;
// Settings
@property (strong, nonatomic) Settings *settings;
// APP Stream 使
@property (weak, nonatomic) StreamRecorder *streamRecorder;
//
@property (nonatomic, assign) long talkingFinisheTimestamp;
//
@property (nonatomic, assign) NSInteger stressSceneId;
@property (assign, nonatomic) BOOL stressStarted;
@property (nonatomic, strong) NSThread *testThread;
@end
@implementation TestAsrViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.startButton.enabled = TRUE;
self.stopButton.enabled = TRUE;
self.stressStarted = FALSE;
self.testThread = nil;
[self decorateTextView:self.resultTextView];
[self.resultTextView setTextColor:UIColor.blackColor];
[ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
self.streamRecorder = [ViewController getStreamRecorder];
self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_TEST_ASR_STRESS];
}
- (void)viewDidDisappear:(BOOL)animated {
[self uninitEngine];
[super viewDidDisappear:animated];
}
- (void)decorateTextView:(UITextView *)textView {
textView.layer.cornerRadius = 5.0f;
textView.layer.borderWidth = .25f;
textView.layer.borderColor = [UIColor grayColor].CGColor;
}
#pragma mark - Config & Init & Uninit Methods
-(void)configInitParams{
//Engine Name
[self.curEngine setStringParam:SE_ASR_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
//Debug & Log
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
[self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
//UID & deviceID: 线
[self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
[self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
//
[self.curEngine setStringParam:[self.settings getOptionsValue:SETTING_RECORD_TYPE] forKey:SE_PARAMS_KEY_RECORDER_TYPE_STRING];
if ([self.settings getBool:SETTING_ASR_RECORDER_SAVE]) {
//SDK .wav
[self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_ASR_REC_PATH_STRING];
}
// RECORDER_TYPE_STREAM 16K
if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_STREAM) {
if ([self.streamRecorder getSampleRate] != 16000) {
// RECORDER_TYPE_STREAM
// SDK
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ENABLE_RESAMPLER_BOOL];
// APP
[self.curEngine setIntParam:[self.streamRecorder getSampleRate] forKey:SE_PARAMS_KEY_CUSTOM_SAMPLE_RATE_INT];
}
}
NSString *address = [self.settings getString:SETTING_ADDRESS];
if (!address.length) {
address = SDEF_DEFAULT_ADDRESS;
}
NSLog(@"Current address: %@", address);
//
[self.curEngine setStringParam:address forKey:SE_PARAMS_KEY_ASR_ADDRESS_STRING];
NSString *uri = [self.settings getString:SETTING_URI];
if (!uri.length) {
uri = SDEF_ASR_DEFAULT_URI;
}
NSLog(@"Current uri: %@", uri);
//Uri
[self.curEngine setStringParam:uri forKey:SE_PARAMS_KEY_ASR_URI_STRING];
NSString* appID = [self.settings getString:SETTING_APPID];
//Appid
[self.curEngine setStringParam:appID.length <= 0 ? SDEF_APPID : appID forKey:SE_PARAMS_KEY_APP_ID_STRING];
//Token
NSString* token = [self.settings getString:SETTING_TOKEN];
[self.curEngine setStringParam:token.length <= 0 ? SDEF_TOKEN : token forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
NSString* cluster = [self.settings getString:SETTING_CLUSTER];
NSLog(@"Current cluster: %@", cluster);
//
[self.curEngine setStringParam:cluster.length <= 0 ? SDEF_ASR_DEFAULT_CLUSTER : cluster forKey:SE_PARAMS_KEY_ASR_CLUSTER_STRING];
//线使
[self.curEngine setIntParam:3000 forKey:SE_PARAMS_KEY_ASR_CONN_TIMEOUT_INT];
[self.curEngine setIntParam:5000 forKey:SE_PARAMS_KEY_ASR_RECV_TIMEOUT_INT];
NSString* recPath = @"";
if ([self.settings getBool:SETTING_RECORD_SAVE]) {
recPath = self.debugPath;
}
[self.curEngine setStringParam:recPath forKey:SE_PARAMS_KEY_REC_PATH_STRING];
[self.curEngine setIntParam:[self.settings getOptions:SETTING_RECORD_FILE_TYPE].chooseIdx forKey:SE_PARAMS_KEY_REC_FILE_TYPE_INT];
}
-(void)configStartAsrParams{
//
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_DDC] forKey:SE_PARAMS_KEY_ASR_ENABLE_DDC_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_ITN] forKey:SE_PARAMS_KEY_ASR_ENABLE_ITN_BOOL];
[self.curEngine setBoolParam:[self.settings getBool:SETTING_ASR_ENABLE_NLU_PUNC] forKey:SE_PARAMS_KEY_ASR_SHOW_NLU_PUNC_BOOL];
//
[self.curEngine setStringParam:[self.settings getOptionsValue:SETTING_ASR_RESULT_TYPE] forKey:SE_PARAMS_KEY_ASR_RESULT_TYPE_STRING];
// ASR VAD
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_VAD_START_SILENCE_TIME] forKey:SE_PARAMS_KEY_ASR_VAD_START_SILENCE_TIME_INT];
[self.curEngine setIntParam:[self.settings getInt:SETTING_ASR_VAD_END_SILENCE_TIME] forKey:SE_PARAMS_KEY_ASR_VAD_END_SILENCE_TIME_INT];
[self.curEngine setStringParam:[self.settings getString:SETTING_ASR_VAD_MODE] forKey:SE_PARAMS_KEY_ASR_VAD_MODE_STRING];
// 150000ms.
[self.curEngine setIntParam:[self.settings getInt:SETTING_VAD_MAX_SPEECH_DURATION] forKey:SE_PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT];
// APP
[self.curEngine setBoolParam:[self.settings getBool:SETTING_GET_VOLUME] forKey:SE_PARAMS_KEY_ENABLE_GET_VOLUME_BOOL];
// ASR
if ([self.settings getString:SETTING_ASR_HOTWORDS].length != 0) {
[self setHotWords:[self.settings getString:SETTING_ASR_HOTWORDS]];
}
if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_STREAM) {
if (![self.streamRecorder start]) {
[self speechEngineNoPermission];
}
} else if ([self.settings getOptionsValue:SETTING_RECORD_TYPE] == SE_RECORDER_TYPE_FILE) {
// 使
NSString* file_path = [NSString stringWithFormat:@"%@/%@", self.debugPath, @"asr_rec_file.pcm"];
NSLog(@"输入的音频文件路径: %@", file_path);
// 使
[self.curEngine setStringParam:file_path forKey:SE_PARAMS_KEY_RECORDER_FILE_STRING];
}
}
- (void)setHotWords:(NSString*) hotWords {
[self.curEngine sendDirective:SEDirectiveUpdateAsrHotWords data: hotWords];
}
- (void)initEngine {
NSLog(@"获取设备ID调试使用");
AppDelegate *appDelegate = [ViewController getAppDelegate];
if (appDelegate == nil) {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
[ViewController setAppDelegate:appDelegate];
self.deviceID = appDelegate.deviceID;
if ([self.deviceID isEqualToString:@""]) {
self.deviceID = @"speech_test_123";
}
NSLog(@"获取设备ID成功: %@", self.deviceID);
NSLog(@"创建引擎");
if (self.curEngine == nil) {
self.curEngine = [[SpeechEngine alloc] init];
if (![self.curEngine createEngineWithDelegate:self]) {
NSLog(@"引擎创建失败.");
return;
}
}
NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"当前调试路径: %@", self.debugPath);
NSLog(@"配置初始化参数");
[self configInitParams];
NSLog(@"引擎初始化");
SEEngineErrorCode ret = [self.curEngine initEngine];
if (ret == SENoError) {
NSLog(@"初始化成功");
} else {
NSLog(@"初始化失败,返回值: %d", ret);
}
}
- (void)uninitEngine {
if (self.curEngine != nil) {
NSLog(@"引擎析构");
[self.curEngine destroyEngine];
self.curEngine = nil;
NSLog(@"引擎析构完成");
}
}
- (void)startEngine {
NSLog(@"配置启动参数");
[self configStartAsrParams];
//
NSLog(@"开启 ASR 云端自动判停");
[self.curEngine setBoolParam:TRUE forKey:SE_PARAMS_KEY_ASR_AUTO_STOP_BOOL];
NSLog(@"启用引擎.");
NSLog(@"Directive: SEDirectiveStartEngine");
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine];
if (ret == SERecCheckEnvironmentFailed) {
[self speechEngineNoPermission];
}
}
- (void)stopEngine {
NSLog(@"异步关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveStopEngine];
}
- (void)syncStopEngine {
NSLog(@"同步关闭引擎");
NSLog(@"Directive: SEDirectiveStopEngine");
[self.curEngine sendDirective:SEDirectiveSyncStopEngine];
}
- (void)finishTalking {
NSLog(@"结束音频输入");
NSLog(@"Directive: SEDirectiveFinishTalking");
[self.curEngine sendDirective:SEDirectiveFinishTalking];
}
- (void)speechEngineNoPermission {
NSLog(@"没有音频权限");
}
- (void)sleep:(NSTimeInterval)ti {
[NSThread sleepForTimeInterval:ti];
}
- (int)getRandomValue:(int)a andData:(int)b {
return (arc4random() % (b + 1)) + a;
}
- (void)testScene0 {
[self initEngine];
while (self.stressStarted) {
[self syncStopEngine];
[self startEngine];
[self sleep:[self getRandomValue:0 andData:3]];
if ([self getRandomValue:0 andData:1] == 0) {
[self finishTalking];
} else {
[self stopEngine];
}
[self sleep:[self getRandomValue:0 andData:3]];
}
[self uninitEngine];
}
- (void)testScene1 {
while (self.stressStarted) {
[self initEngine];
[self syncStopEngine];
[self startEngine];
[self sleep:[self getRandomValue:0 andData:3]];
if ([self getRandomValue:0 andData:1] == 0) {
[self finishTalking];
} else {
[self stopEngine];
}
[self sleep:[self getRandomValue:0 andData:3]];
[self uninitEngine];
}
}
- (void)testScene2 {
while (self.stressStarted) {
[self initEngine];
[self syncStopEngine];
[self startEngine];
[self sleep:1];
[self finishTalking];
[self sleep:2];
[self uninitEngine];
}
}
- (void)testScene3 {
[self initEngine];
while (self.stressStarted) {
int method = [self getRandomValue:0 andData:4];
switch (method) {
case 0:
[self startEngine];
break;
case 1:
[self syncStopEngine];
break;
case 2:
[self stopEngine];
break;
case 3:
[self finishTalking];
break;
case 4:
[self uninitEngine];
[self initEngine];
break;
}
[self sleep:[self getRandomValue:0 andData:5]];
}
[self uninitEngine];
}
- (void)test {
NSString* sceneid = [self.settings getOptionsValue:SETTING_ASR_STRESS_SCENEID];
NSArray* sceneType = @[@"正常场景1", @"正常场景2", @"ERROR回调时析构", @"随机压测"];
self.stressSceneId = [sceneType indexOfObject:sceneid];
dispatch_async(dispatch_get_main_queue(), ^{
NSString* str = [@"开始压测 " stringByAppendingString:sceneType[self.stressSceneId]];
[self setResultText:str];
});
switch (self.stressSceneId) {
case 0:
[self testScene0];
break;
case 1:
[self testScene1];
break;
case 2:
[self testScene2];
break;
case 3:
[self testScene3];
break;
default:
break;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSString* str = [@"压测结束 " stringByAppendingString:sceneType[self.stressSceneId]];
[self setResultText:str];
});
}
#pragma mark - UI Actions
- (IBAction)startBtnClicked:(id)sender {
if (self.testThread) {
if (self.testThread.executing) {
NSLog(@"Already start!");
return;
}
[self.testThread cancel];
self.testThread = nil;
}
self.stressStarted = TRUE;
self.testThread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[self.testThread start];
}
- (IBAction)stopBtnClicked:(id)sender {
if (!self.testThread) {
NSLog(@"Not start yet!");
return;
}
self.stressStarted = FALSE;
}
#pragma mark - Message Callback
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
NSLog(@"Message Type: %d.", type);
switch (type) {
case SEEngineStart:
// Callback:
NSLog(@"Callback: 引擎启动成功");
break;
case SEEngineStop:
// Callback:
NSLog(@"Callback: 引擎关闭");
break;
case SEEngineError:
// Callback:
NSLog(@"Callback: 错误信息: %@", data);
[self speechEngineError:data];
break;
case SEAsrPartialResult:
// Callback: ASR
NSLog(@"Callback: ASR 当前请求的部分结果");
break;
case SEFinalResult:
// Callback: ASR
NSLog(@"Callback: ASR 当前请求最终结果");
[self speechEngineResult:data isFinal:true];
break;
case SEVolumeLevel:
// Callback:
NSLog(@"Callback: 录音音量");
break;
default:
break;
}
}
- (void)speechEngineError:(NSData *)data {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.stressSceneId == 2) {
if ([self getRandomValue:0 andData:1] == 0) {
[self stopEngine];
}
[self uninitEngine];
}
});
}
- (void)speechEngineResult:(NSData *)data isFinal:(BOOL)isFinal {
dispatch_async(dispatch_get_main_queue(), ^{
if (isFinal && self.stressSceneId == 2) {
if ([self getRandomValue:0 andData:1] == 0) {
[self stopEngine];
}
[self uninitEngine];
}
});
}
#pragma mark - Helper
- (NSString *)getRecorderType {
SettingOptions* recorderTypeOptions = [self.settings getOptions:SETTING_RECORD_TYPE];
switch (recorderTypeOptions.chooseIdx) {
case 0:
return SE_RECORDER_TYPE_RECORDER;
case 1:
return SE_RECORDER_TYPE_FILE;
case 2:
return SE_RECORDER_TYPE_STREAM;
default:
break;
}
return @"";
}
- (void)setResultText:(NSString *)result {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resultTextView setText:[result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
});
}
- (long)timeDelayFrom:(long)pastTimestamp {
return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
id nextPage = [segue destinationViewController];
[nextPage setValue:VIEW_TEST_ASR_STRESS forKey:@"viewId"];
}
@end

Binary file not shown.

View File

@ -0,0 +1,107 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_40_40.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_60_60.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_58_58.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_87_87.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_80_80.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_120_120.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_120_120-1.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "asr_lark_bot_ico_180_180.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "asr_lark_bot_ico_1024_1024.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "asr_60_60.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "asr_120_120.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "asr_180_180.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "capt_60_60.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "capt_120_120.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "capt_180_180.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "fulllink_60_60.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "fulllink_120_120.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "fulllink_180_180.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "settings.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "settings-1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "settings-2.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "tts_60_60.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tts_120_120.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "tts_180_180.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

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