Add convAI plugin

This commit is contained in:
2026-02-19 12:44:26 +01:00
parent 457cd803c1
commit 61710c9fde
3434 changed files with 955067 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
using UnrealBuildTool;
using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
// using Tools.DotNETCommon;
public class Convai : ModuleRules
{
private static ConvaiPlatform ConvaiPlatformInstance;
private string ModulePath
{
get { return ModuleDirectory; }
}
private string ThirdPartyPath
{
get { return Path.GetFullPath(Path.Combine(ModulePath, "../ThirdParty/")); }
}
private ConvaiPlatform GetConvaiPlatformInstance(ReadOnlyTargetRules Target)
{
var ConvaiPlatformType = System.Type.GetType("ConvaiPlatform_" + Target.Platform.ToString());
if (ConvaiPlatformType == null)
{
throw new BuildException("Convai does not support platform " + Target.Platform.ToString());
}
var PlatformInstance = Activator.CreateInstance(ConvaiPlatformType) as ConvaiPlatform;
if (PlatformInstance == null)
{
throw new BuildException("Convai could not instantiate platform " + Target.Platform.ToString());
}
return PlatformInstance;
}
private bool ConfigurePlatform(ReadOnlyTargetRules Target, UnrealTargetConfiguration Configuration)
{
//Convai thirdparty libraries root path
string root = ThirdPartyPath;
foreach (var arch in ConvaiPlatformInstance.Architectures())
{
string grpcPath = root + "gRPC/" + "lib/" + ConvaiPlatformInstance.LibrariesPath + arch;
// Add files that end with .lib
PublicAdditionalLibraries.AddRange(Directory.GetFiles(grpcPath, "*.lib"));
// Add files that end with .a
PublicAdditionalLibraries.AddRange(Directory.GetFiles(grpcPath, "*.a"));
}
return false;
}
public Convai(ReadOnlyTargetRules Target) : base(Target)
{
// Common Settings
DefaultBuildSettings = BuildSettingsVersion.Latest;
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PrecompileForTargets = PrecompileTargetsType.Any;
ConvaiPlatformInstance = GetConvaiPlatformInstance(Target);
PrivateIncludePaths.Add("Convai/Private");
if (Target.Platform == UnrealTargetPlatform.Mac || Target.Platform == UnrealTargetPlatform.IOS)
{
// Include .mm file only for Mac
PrivateIncludePaths.Add("Convai/Private/Mac");
}
const bool bEnableConvaiHTTP = false;
PublicDefinitions.AddRange(new string[] { "USE_CONVAI_HTTP=0" + (bEnableConvaiHTTP ? "1" : "0")});
if (bEnableConvaiHTTP)
{
PublicDependencyModuleNames.AddRange(new string[] { "CONVAIHTTP", "HTTP" });
}
else
{
PublicDependencyModuleNames.AddRange(new string[] { "HTTP" });
}
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json", "JsonUtilities", "AudioMixer", "AudioCaptureCore", "AudioCapture", "Voice", "SignalProcessing", "libOpus", "OpenSSL", "zlib", "SSL" });
PrivateDependencyModuleNames.AddRange(new string[] {"Projects"});
PublicDefinitions.AddRange(new string[] { "ConvaiDebugMode=1", "GOOGLE_PROTOBUF_NO_RTTI", "GPR_FORBID_UNREACHABLE_CODE", "GRPC_ALLOW_EXCEPTIONS=0" });
// Target Platform Specific Settings
if (Target.Platform == UnrealTargetPlatform.Win64)
{
bUsePrecompiled = false;
}
if (Target.Platform == UnrealTargetPlatform.Android)
{
PublicDependencyModuleNames.AddRange(new string[] { "ApplicationCore", "AndroidPermission" });
string BuildPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(BuildPath, "Convai_AndroidAPL.xml"));
}
if (Target.Platform == UnrealTargetPlatform.Mac || Target.Platform == UnrealTargetPlatform.IOS || Target.Platform == UnrealTargetPlatform.Linux)
{
PublicDefinitions.AddRange(new string[] { "GOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0", "GOOGLE_PROTOBUF_USE_UNALIGNED=0", "PROTOBUF_ENABLE_DEBUG_LOGGING_MAY_LEAK_PII=0" });
PrivateIncludePaths.AddRange(new string[] { Path.Combine(ThirdPartyPath, "gRPC", "Include_1.50.x") });
}
else
{
PrivateIncludePaths.AddRange(new string[] { Path.Combine(ThirdPartyPath, "gRPC", "Include") });
}
// ThirdParty Libraries
AddEngineThirdPartyPrivateStaticDependencies(Target, "libOpus");
AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenSSL");
AddEngineThirdPartyPrivateStaticDependencies(Target, "zlib");
ConfigurePlatform(Target, Target.Configuration);
}
}
public abstract class ConvaiPlatform
{
public virtual string ConfigurationDir(UnrealTargetConfiguration Configuration)
{
if (Configuration == UnrealTargetConfiguration.Debug || Configuration == UnrealTargetConfiguration.DebugGame)
{
return "Debug/";
}
else
{
return "Release/";
}
}
public abstract string LibrariesPath { get; }
public abstract List<string> Architectures();
public abstract string LibraryPrefixName { get; }
public abstract string LibraryPostfixName { get; }
}
public class ConvaiPlatform_Win64 : ConvaiPlatform
{
public override string LibrariesPath { get { return "win64/"; } }
public override List<string> Architectures() { return new List<string> { "" }; }
public override string LibraryPrefixName { get { return ""; } }
public override string LibraryPostfixName { get { return ".lib"; } }
}
public class ConvaiPlatform_Android : ConvaiPlatform
{
public override string LibrariesPath { get { return "android/"; } }
public override List<string> Architectures() { return new List<string> { "armeabi-v7a/", "arm64-v8a/", "x86_64/" }; }
public override string LibraryPrefixName { get { return "lib"; } }
public override string LibraryPostfixName { get { return ".a"; } }
}
public class ConvaiPlatform_Mac : ConvaiPlatform
{
public override string LibrariesPath { get { return "mac/"; } }
public override List<string> Architectures() { return new List<string> { "" }; }
public override string LibraryPrefixName { get { return "lib"; } }
public override string LibraryPostfixName { get { return ".a"; } }
}
public class ConvaiPlatform_Linux : ConvaiPlatform
{
public override string LibrariesPath { get { return "linux/"; } }
public override List<string> Architectures() { return new List<string> { "" }; }
public override string LibraryPrefixName { get { return "lib"; } }
public override string LibraryPostfixName { get { return ".a"; } }
}
//public class ConvaiPlatform_PS5 : ConvaiPlatform
//{
// public override string LibrariesPath { get { return "ps5/"; } }
// public override List<string> Architectures() { return new List<string> { "" }; }
// public override string LibraryPrefixName { get { return "lib"; } }
// public override string LibraryPostfixName { get { return ".a"; } }
//}
//public class ConvaiPlatform_IOS : ConvaiPlatform
//{
// public override string ConfigurationDir(UnrealTargetConfiguration Configuration)
// {
// return "";
// }
// public override string LibrariesPath { get { return "ios/"; } }
// public override List<string> Architectures() { return new List<string> { "" }; }
// public override string LibraryPrefixName { get { return "lib"; } }
// public override string LibraryPostfixName { get { return ".a"; } }
//}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "Convai.h"
#include "Developer/Settings/Public/ISettingsModule.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/Package.h"
IMPLEMENT_MODULE(Convai, Convai);
#define LOCTEXT_NAMESPACE "Convai"
void Convai::StartupModule()
{
ConvaiSettings = NewObject<UConvaiSettings>(GetTransientPackage(), "ConvaiSettings", RF_Standalone);
ConvaiSettings->AddToRoot();
// Register settings
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->RegisterSettings("Project", "Plugins", "Convai",
LOCTEXT("RuntimeSettingsName", "Convai"),
LOCTEXT("RuntimeSettingsDescription", "Configure Convai settings"),
ConvaiSettings);
}
}
void Convai::ShutdownModule()
{
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->UnregisterSettings("Project", "Plugins", "Convai");
}
if (!GExitPurge)
{
ConvaiSettings->RemoveFromRoot();
}
else
{
ConvaiSettings = nullptr;
}
}
UConvaiSettings* Convai::GetConvaiSettings() const
{
check(ConvaiSettings);
return ConvaiSettings;
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,108 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "Public/ConvaiDefinitions.h"
#include "Convai.generated.h"
UCLASS(config = Engine, defaultconfig)
class CONVAI_API UConvaiSettings : public UObject
{
GENERATED_BODY()
public:
UConvaiSettings(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
API_Key = "";
EnableNewActionSystem = false;
}
/* API Key Issued from the website */
UPROPERTY(Config, EditAnywhere, Category = "Convai API")
FString API_Key;
/* Enable new actions system */
UPROPERTY(Config, EditAnywhere, Category = "Convai API", meta = (DisplayName = "Enable New Action System (Experimental)"))
bool EnableNewActionSystem;
/* Authentication token used for Convai Connect */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
FString AuthToken;
/* Custom Server URL (Used for debugging) */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
FString CustomURL;
/* Custom Beta API URL (Used for debugging) */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
FString CustomBetaURL;
/* Custom Production API URL (Used for debugging) */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
FString CustomProdURL;
/* Test Character ID (Used for debugging) */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
FString TestCharacterID;
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
bool AllowInsecureConnection;
/* Use both hardcoded and system SSL certificates on Windows */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
bool UseSystemCertificates;
/* Extra Parameters (Used for debugging) */
UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "Convai API")
FString ExtraParams;
UPROPERTY(Config, VisibleAnywhere, Category = "Long Term Memory")
TArray<FConvaiSpeakerInfo> SpeakerIDs;
};
class CONVAI_API Convai : public IModuleInterface
{
public:
/** IModuleInterface implementation */
void StartupModule();
void ShutdownModule();
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline Convai& Get()
{
return FModuleManager::LoadModuleChecked<Convai>("Convai");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("Convai");
}
virtual bool IsGameModule() const override
{
return true;
}
/** Getter for internal settings object to support runtime configuration changes */
UConvaiSettings* GetConvaiSettings() const;
protected:
/** Module settings */
UConvaiSettings* ConvaiSettings;
};

View File

@@ -0,0 +1,301 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- // Copyright 2022 Convai Inc. All Rights Reserved. -->
<root xmlns:android="http://schemas.android.com/apk/res/android">
<!-- init section is always evaluated once per architecture -->
<trace enable="true"/>
<init>
<log text="AndroidAPITemplate init"/>
</init>
<androidManifestUpdates>
<addPermission android:name="android.permission.RECORD_AUDIO" />
</androidManifestUpdates>
<!-- optional additions to proguard -->
<proguardAdditions>
<insert><![CDATA[
-keepattributes Signature
-dontskipnonpubliclibraryclassmembers
-keepclassmembers class com.epicgames.ue4.GameActivity {
public <methods>;
public <fields>;
}
]]></insert>
</proguardAdditions>
<gradleProperties>
<insert>
android.useAndroidX=true
android.enableJetifier=true
</insert>
</gradleProperties>
<buildscriptGradleAdditions>
<insert>
dependencies
{
repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository
}
classpath 'com.google.gms:google-services:4.3.3' // google-services plugin
}
</insert>
</buildscriptGradleAdditions>
<buildGradleAdditions>
<!-- Needs to be same version number as play-services -->
<insert>
dependencies
{
implementation 'com.google.android.play:app-update:2.1.0'
implementation 'com.google.android.play:review:2.0.1'
}
allprojects {
def mappings = [
'android.support.annotation': 'androidx.annotation',
'android.arch.lifecycle': 'androidx.lifecycle',
'android.support.v4.app.NotificationCompat': 'androidx.core.app.NotificationCompat',
'android.support.v4.app.ActivityCompat': 'androidx.core.app.ActivityCompat',
'android.support.v4.content.ContextCompat': 'androidx.core.content.ContextCompat',
'android.support.v13.app.FragmentCompat': 'androidx.legacy.app.FragmentCompat',
'android.arch.lifecycle.Lifecycle': 'androidx.lifecycle.Lifecycle',
'android.arch.lifecycle.LifecycleObserver': 'androidx.lifecycle.LifecycleObserver',
'android.arch.lifecycle.OnLifecycleEvent': 'androidx.lifecycle.OnLifecycleEvent',
'android.arch.lifecycle.ProcessLifecycleOwner': 'androidx.lifecycle.ProcessLifecycleOwner',
]
beforeEvaluate { project ->
project.rootProject.projectDir.traverse(type: groovy.io.FileType.FILES, nameFilter: ~/.*\.java$/) { f ->
mappings.each { entry ->
if (f.getText('UTF-8').contains(entry.key)) {
println "Updating ${entry.key} to ${entry.value} in file ${f}"
ant.replace(file: f, token: entry.key, value: entry.value)
}
}
}
}
}
</insert>
</buildGradleAdditions>
<resourceCopies>
<!-- Copy the generated resource file to be packaged -->
</resourceCopies>
<AARImports>
</AARImports>
<!-- optional additions to the GameActivity imports in GameActivity.java -->
<gameActivityImportAdditions>
<insert>
import java.util.HashSet;
import java.util.Arrays;
import android.os.Handler;
import android.util.Log;
import android.media.AudioRecord;
import android.media.AudioFormat;
import android.media.MediaRecorder;
import android.media.AudioTrack;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.media.audiofx.AcousticEchoCanceler;
</insert>
</gameActivityImportAdditions>
<!-- optional additions to the GameActivity class in GameActivity.java -->
<gameActivityClassAdditions>
<insert>
<![CDATA[
/* JNI UE4 Native */
private static native void dataMicPayload(short[] dataMic, int readSize);
/* VAR */
AudioRecord microphone = null;
Thread threadMicro = null;
AudioTrack audioplayback;
RunnableVoice runableVoice;
private boolean isPermissionAllowedMic = false;
/* FUNC */
public void AndroidThunkJava_AndroidAskPermissionMicrophone(){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 1);
}
public boolean AndroidThunkJava_AndroidHasPermissionMicrophone(){
boolean res = false;
res = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
android.util.Log.d("AndroidRateApp","UPL permission " + res);
return res;
}
public boolean AndroidThunkJava_AndroidStartMicrophone(int sampleRateToUse) {
android.util.Log.d("AndroidRateApp","UPL microphone sampleRateToUse " + sampleRateToUse);
if(!isPermissionAllowedMic){
isPermissionAllowedMic = AndroidThunkJava_AndroidHasPermissionMicrophone();
if(!isPermissionAllowedMic) return false;
}
if(microphone == null){
final int sampleRate = sampleRateToUse;
final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
final int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
microphone = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, sampleRate, channelConfig, audioFormat, minBufferSize );
if(AcousticEchoCanceler.isAvailable()){
AcousticEchoCanceler aec = AcousticEchoCanceler.create(microphone.getAudioSessionId());
if (aec != null) {
aec.setEnabled(true);
android.util.Log.d("AndroidRateApp","echo cancelation ok VOICE_COMMUNICATION");
} else {
android.util.Log.d("AndroidRateApp","echo cancelation not ok VOICE_COMMUNICATION");
}
}
microphone.startRecording();
runableVoice = new RunnableVoice(minBufferSize);
runableVoice.microphoneThread = microphone;
threadMicro = new Thread(runableVoice);
threadMicro.start();
}
microphone.startRecording();
runableVoice.setthreadMicroRunning(true);
return true;
}
public void AndroidThunkJava_AndroidStopMicrophone() {
android.util.Log.d("AndroidRateApp","UPL AndroidThunkJava_AndroidStopMicrophone setthreadMicroRunning");
runableVoice.setthreadMicroRunning(false);
//threadMicro.stop();
microphone.stop();
runableVoice.setthreadMicroRunning(false);
//microphone.release();
}
public class RunnableVoice implements Runnable {
public volatile boolean threadMicroRunning = false;
public AudioRecord microphoneThread = null;
int minBufferSize;
public RunnableVoice(int minBufferSize){
this.minBufferSize = minBufferSize;
}
public void setthreadMicroRunning(boolean threadMicroRunning){
this.threadMicroRunning = threadMicroRunning;
android.util.Log.d("AndroidRateApp","UPL setthreadMicroRunning " + this.threadMicroRunning);
}
@Override
public void run() {
try {
short[] buffer = new short[minBufferSize / 2];
while (true) {
if(threadMicroRunning){
int readSize = microphoneThread.read(buffer, 0, buffer.length);
android.util.Log.d("AndroidRateApp","UPL microphone data / 2 " + readSize +" "+buffer[0]);
dataMicPayload(buffer, readSize);
}
}
} catch(Exception e){
e.printStackTrace();
}
}
}
]]>
</insert>
</gameActivityClassAdditions>
<!-- optional additions to GameActivity onCreate metadata reading in GameActivity.java -->
<gameActivityReadMetadataAdditions>
<insert>
</insert>
</gameActivityReadMetadataAdditions>
<!-- optional additions to GameActivity onCreate in GameActivity.java -->
<gameActivityOnCreateAdditions>
<insert>
<![CDATA[
]]>
</insert>
</gameActivityOnCreateAdditions>
<!-- optional additions to GameActivity onDestroy in GameActivity.java -->
<gameActivityOnDestroyAdditions>
<insert>
</insert>
</gameActivityOnDestroyAdditions>
<!-- optional additions to GameActivity onStart in GameActivity.java -->
<gameActivityOnStartAdditions>
<insert>
</insert>
</gameActivityOnStartAdditions>
<!-- optional additions to GameActivity onStop in GameActivity.java -->
<gameActivityOnStopAdditions>
<insert>
</insert>
</gameActivityOnStopAdditions>
<!-- optional additions to GameActivity onPause in GameActivity.java -->
<gameActivityOnPauseAdditions>
<insert>
<![CDATA[
]]>
</insert>
</gameActivityOnPauseAdditions>
<!-- optional additions to GameActivity onResume in GameActivity.java -->
<gameActivityOnResumeAdditions>
<insert>
</insert>
</gameActivityOnResumeAdditions>
<!-- optional additions to GameActivity onActivityResult in GameActivity.java -->
<gameActivityOnActivityResultAdditions>
<insert>
</insert>
</gameActivityOnActivityResultAdditions>
<!-- optional libraries to load in GameActivity.java before libUE4.so -->
<soLoadLibrary>
<!-- need this if plugin enabled and supported architecture, even if not packaged for GearVR -->
</soLoadLibrary>
</root>

View File

@@ -0,0 +1,445 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiActionUtils.h"
#include "ConvaiUtils.h"
#include "Internationalization/Regex.h"
#include "ConvaiDefinitions.h"
DEFINE_LOG_CATEGORY(ConvaiActionUtilsLog);
namespace
{
bool FindCharAfterIndex(const FString& SearchString, const char Search, int32& OutIndex, const int32& AfterIndex)
{
return SearchString.Left(AfterIndex).FindChar(Search, OutIndex);
}
FString RemoveQuotedWords(const FString& Input)
{
int32 QuoteStartIndex = -1;
if (!Input.FindChar('"', QuoteStartIndex))
{
return Input;
}
FString Result = Input;
while (Result.FindChar('"', QuoteStartIndex))
{
int32 QuoteEndIndex = -1;
// Start searching for the end quote after the start quote
if (Result.Left(QuoteStartIndex + 1).FindChar('"', QuoteEndIndex) && QuoteEndIndex > QuoteStartIndex)
{
// Remove the quoted string, including the quotes
Result.RemoveAt(QuoteStartIndex, QuoteEndIndex - QuoteStartIndex + 1);
}
else
{
// No closing quote found after this point, break the loop
break;
}
}
return Result;
}
int32 CountWords(const FString& str)
{
if (str.Len() == 0) return 0;
TArray<FString> words;
str.ParseIntoArray(words, TEXT(" "), true);
return words.Num();
}
FString KeepNWords(const FString& StringToBeParsed, const FString& CandidateString)
{
TArray<FString> words;
StringToBeParsed.ParseIntoArray(words, TEXT(" "), true);
int32 N = CountWords(CandidateString);
FString result;
for (int32 i = 0; i < FMath::Min(N, words.Num()); i++)
{
result += words[i] + " ";
}
// Trim trailing space
result = result.LeftChop(1);
return result;
}
FString FindClosestString(FString Input, const TArray<FString>& StringArray)
{
FString ClosestString;
int32 MinDistance = MAX_int32;
for (auto& String : StringArray)
{
int32 Distance = UConvaiUtils::LevenshteinDistance(Input, String);
if (Distance < MinDistance)
{
MinDistance = Distance;
ClosestString = String;
}
}
return ClosestString;
}
// Helper function to check if a substring is found outside of quotes.
bool FindSubstringOutsideQuotes(const FString& SearchString, const FString& SubstringToFind)
{
bool bInsideQuotes = false;
int32 SubstringLength = SubstringToFind.Len();
int32 SearchStringLength = SearchString.Len();
if (SubstringLength > SearchStringLength || SubstringLength == 0)
{
return false;
}
for (int32 i = 0; i <= SearchStringLength - SubstringLength; ++i)
{
// Toggle the inside quotes flag if a quote is found
if (SearchString[i] == '"')
{
bInsideQuotes = !bInsideQuotes;
continue;
}
// If we are not inside quotes, perform the search
if (!bInsideQuotes && SearchString.Mid(i, SubstringLength).Equals(SubstringToFind, ESearchCase::IgnoreCase))
{
// Make sure the match is not part of a larger string
if ((i == 0 || !FChar::IsAlnum(SearchString[i - 1])) &&
(i + SubstringLength == SearchStringLength || !FChar::IsAlnum(SearchString[i + SubstringLength])))
{
return true; // Substring found outside of quotes
}
}
}
return false; // Substring not found or is within quotes
}
bool FindClosePhraseOutsideQuotes(const FString& SearchString, const FString& PhraseToFind, int& OutBestDistance, int NumWordsToSkip = 0)
{
if (PhraseToFind.IsEmpty())
{
return false; // Can't find an empty phrase.
}
int32 MaxLevenshteinDistance = FMath::Clamp(PhraseToFind.Len() / 2, 2, 4);
TArray<FString> Words;
FString CurrentWord;
bool bInsideQuotes = false;
OutBestDistance = MaxLevenshteinDistance + 1; // Initialize with a value above the limit
// Collect words outside quotes
for (TCHAR Char : SearchString)
{
if (Char == '"')
{
bInsideQuotes = !bInsideQuotes;
if (!CurrentWord.IsEmpty())
{
Words.Add(CurrentWord);
CurrentWord.Empty();
}
continue;
}
if (!bInsideQuotes && (Char == ' ' || Char == '\t'))
{
if (!CurrentWord.IsEmpty())
{
Words.Add(CurrentWord);
CurrentWord.Empty();
}
}
else if (!bInsideQuotes)
{
CurrentWord.AppendChar(Char);
}
}
// Add the last word if there is one
if (!CurrentWord.IsEmpty())
{
Words.Add(CurrentWord);
}
// Number of words in the phrase to find
TArray<FString> PhraseWords;
PhraseToFind.ParseIntoArray(PhraseWords, TEXT(" "), true);
int32 NumWordsInPhrase = PhraseWords.Num();
// Sliding window of words to check for the phrase
for (int32 i = NumWordsToSkip; i <= Words.Num() - NumWordsInPhrase; ++i)
{
FString WindowString;
for (int32 j = 0; j < NumWordsInPhrase; ++j)
{
WindowString += (j > 0 ? TEXT(" ") : TEXT("")) + Words[i + j];
}
if (WindowString.Len() - PhraseToFind.Len() >= MaxLevenshteinDistance || PhraseToFind.Len() - WindowString.Len() >= MaxLevenshteinDistance)
{
continue;
}
int32 Distance = UConvaiUtils::LevenshteinDistance(WindowString, PhraseToFind);
if (Distance <= MaxLevenshteinDistance && Distance < OutBestDistance)
{
OutBestDistance = Distance;
}
}
// The function returns true if we found a match within the acceptable Levenshtein distance
return OutBestDistance <= MaxLevenshteinDistance;
}
static bool FindNearestObjectByName(FString SearchString, TArray<FConvaiObjectEntry> Objects, FConvaiObjectEntry& ObjectMatch)
{
FString SearchStringLower = SearchString.ToLower();
bool Found = false;
int BestDistance = 100;
for (auto o : Objects)
{
FString ObjectNameLower = o.Name.ToLower();
int Distance = 0;
if (FindClosePhraseOutsideQuotes(SearchStringLower, ObjectNameLower, Distance))
{
if (Distance < BestDistance)
{
ObjectMatch = o;
BestDistance = Distance;
Found = true;
}
}
}
// Try to search in objects using each word in the SearchString
if (!Found)
{
TArray<FString> words;
FString SearchStringWithoutQuotes = RemoveQuotedWords(SearchStringLower);
SearchStringWithoutQuotes.ParseIntoArray(words, TEXT(" "), true);
bool breakFromLoop = false;
for (auto o : Objects)
{
if (breakFromLoop)
break;
if (CountWords(o.Name) <= 1)
continue; // consider only names consisting of 1+ words
FString ObjectNameLower = o.Name.ToLower();
for (auto w : words)
{
if (w.Len() <= 3)
continue; // consider only words greater than 3 letters
int Distance = 0;
if (FindClosePhraseOutsideQuotes(ObjectNameLower, w, Distance))
{
ObjectMatch = o;
BestDistance = Distance;
Found = true;
breakFromLoop = true;
break;
}
}
}
}
return Found;
}
TArray<FConvaiObjectEntry> BubbleSortEntriesByNumberOfWords(TArray<FConvaiObjectEntry> Entries)
{
bool swapped;
int n = Entries.Num();
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (CountWords(Entries[j].Name) > CountWords(Entries[j+1].Name)) {
Entries.Swap(j, j + 1);
swapped = true;
}
}
// If no two elements were swapped by inner loop, then break
if (!swapped) {
break;
}
}
return Entries;
}
};
TArray<FString> UConvaiActions::SmartSplit(const FString& SequenceString)
{
TArray<FString> Result;
FString CurrentString = "";
bool InQuotes = false;
for (int i = 0; i < SequenceString.Len(); ++i)
{
TCHAR CurrentChar = SequenceString[i];
if (CurrentChar == '\"')
{
InQuotes = !InQuotes;
}
if (CurrentChar == ',' && !InQuotes)
{
Result.Add(CurrentString.TrimStartAndEnd());
CurrentString = "";
}
else
{
CurrentString += CurrentChar;
}
}
if (!CurrentString.IsEmpty())
{
Result.Add(CurrentString.TrimStartAndEnd());
}
return Result;
}
FString UConvaiActions::ExtractText(FString Action, FString ActionResult)
{
FString ExtraText = "";
FRegexPattern TextPattern(TEXT("\".*\""));
FRegexMatcher StringMatcher = FRegexMatcher(TextPattern, ActionResult);
if (StringMatcher.FindNext())
{
//CONVAI_LOG(ConvaiUtilsLog, Warning, TEXT("*StringMatcher.GetCaptureGroup(0):%s"), *StringMatcher.GetCaptureGroup(0));
ExtraText = *StringMatcher.GetCaptureGroup(0).LeftChop(1).RightChop(1);
}
else
{
int index = ActionResult.Find(Action);
index += Action.Len();
ExtraText = ActionResult.RightChop(index + 1);
//ExtraText = ActionResult;
}
return ExtraText;
}
float UConvaiActions::ExtractNumber(FString ActionResult)
{
float ExtraNumber = 0;
FRegexPattern NumericPattern(TEXT("\\d+"));
FRegexMatcher NumberMatcher = FRegexMatcher(NumericPattern, ActionResult);
if (NumberMatcher.FindNext())
{
//CONVAI_LOG(ConvaiGetActionHttpLog, Warning, TEXT("*NumberMatcher.GetCaptureGroup(0):%s"), *NumberMatcher.GetCaptureGroup(0));
ExtraNumber = FCString::Atof(*NumberMatcher.GetCaptureGroup(0));
}
return ExtraNumber;
}
FString UConvaiActions::RemoveDesc(FString str)
{
FRegexPattern DescPattern(TEXT("<.*>"));
FRegexMatcher DescMatcher = FRegexMatcher(DescPattern, str);
int i = 0;
while (DescMatcher.FindNext())
{
int start = DescMatcher.GetMatchBeginning();
str.ReplaceInline(*DescMatcher.GetCaptureGroup(i++), *FString(""));
str.TrimEndInline();
str.TrimStartInline();
}
//CONVAI_LOG(ConvaiActionUtilsLog, Warning, TEXT("str:%s"), *str);
return str;
}
FString UConvaiActions::FindAction(FString ActionToBeParsed, TArray<FString> Actions)
{
FString ClosestAction = "None";
int32 MinDistance = 4;
for (auto a : Actions)
{
FString TrimmedAction = ActionToBeParsed;
a = RemoveDesc(a);
TrimmedAction = KeepNWords(ActionToBeParsed, a);
int32 Distance = UConvaiUtils::LevenshteinDistance(TrimmedAction, a);
if (Distance < MinDistance)
{
MinDistance = Distance;
ClosestAction = a;
}
}
return ClosestAction;
}
bool UConvaiActions::ParseAction(UConvaiEnvironment* Environment, FString ActionToBeParsed, FConvaiResultAction& ConvaiResultAction)
{
FString ActionToAdd;
FConvaiObjectEntry RelatedObjOrChar;
ConvaiResultAction.ActionString = ActionToBeParsed;
// find actions
ActionToAdd = FindAction(ActionToBeParsed, Environment->Actions);
TArray<FConvaiObjectEntry> AllCharsAndObjs;
AllCharsAndObjs.Append(Environment->Characters);
AllCharsAndObjs.Append(Environment->Objects);
// find objects or characters
FindNearestObjectByName(ActionToBeParsed, AllCharsAndObjs, RelatedObjOrChar);
// find objects
//FindNearestObjectByName(ActionToBeParsed, Environment->Objects, RelatedObjOrChar);
// Find extra numeric param
float ExtraNumber = ExtractNumber(ActionToBeParsed);
// Find extra textual param
FString ExtraText = ExtractText(ActionToAdd, ActionToBeParsed);
// Add to result action sequence array
if (ActionToAdd != "")
{
ConvaiResultAction.Action = ActionToAdd;
ConvaiResultAction.RelatedObjectOrCharacter = RelatedObjOrChar;
ConvaiResultAction.ConvaiExtraParams.Number = ExtraNumber;
ConvaiResultAction.ConvaiExtraParams.Text = ExtraText;
return true;
}
return false;
}
bool UConvaiActions::ValidateEnvironment(UConvaiEnvironment* Environment, FString& Error)
{
if (!IsValid(Environment))
{
Error = "ConvaiActions: Environment object is invalid or not set! You can create it using \"Create Environemnt Object\" function";
return false;
}
if (!Environment->MainCharacter.Ref.IsValid())
{
Error = "ConvaiActions: Main Character in the Environment object is invalid or not set! Please set it using \"Set Main Character\" function and use the player pawn as input";
return false;
}
if (Environment->Actions.Num() == 0)
{
Error = "ConvaiActions: The Environment object does not have any actions added, please add some actions using \"Add Action\" or \"Add Actions\" function";
return false;
}
return true;
}

View File

@@ -0,0 +1,31 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiAndroid.h"
//#define PLATFORM_ANDROID 1
#if PLATFORM_ANDROID
#include "AndroidPermissionFunctionLibrary.h"
#endif
DEFINE_LOG_CATEGORY(ConvaiAndroidLog);
void UConvaiAndroid::ConvaiAndroidAskMicrophonePermission()
{
#if PLATFORM_ANDROID
TArray<FString> Permissions;
Permissions.Add((FString("android.permission.RECORD_AUDIO")));
UAndroidPermissionFunctionLibrary::AcquirePermissions(Permissions);
#endif
}
bool UConvaiAndroid::ConvaiAndroidHasMicrophonePermission()
{
#if PLATFORM_ANDROID
return UAndroidPermissionFunctionLibrary::CheckPermission("android.permission.RECORD_AUDIO");
#else
return true;
#endif
}

View File

@@ -0,0 +1,454 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiAudioCaptureComponent.h"
#include "ConvaiDefinitions.h"
#include "Utility/Log/ConvaiLogger.h"
DEFINE_LOG_CATEGORY(ConvaiAudioLog);
namespace Audio
{
FConvaiAudioCaptureSynth::FConvaiAudioCaptureSynth()
: NumSamplesEnqueued(0)
, bInitialized(false)
, bIsCapturing(false)
{
}
FConvaiAudioCaptureSynth::~FConvaiAudioCaptureSynth()
{
}
TArray<FCaptureDeviceInfo> FConvaiAudioCaptureSynth::GetCaptureDevicesAvailable()
{
TArray<FCaptureDeviceInfo> CaptureDevicesInfo;
AudioCapture.GetCaptureDevicesAvailable(CaptureDevicesInfo);
for (int i = CaptureDevicesInfo.Num()-1; i>=0; i--)
{
if (CaptureDevicesInfo[i].InputChannels <= 0)
CaptureDevicesInfo.RemoveAt(i);
}
return CaptureDevicesInfo;
}
int32 FConvaiAudioCaptureSynth::MapInputDeviceIDToDeviceID(int32 InputDeviceID)
{
uint32 NumInputDevices = 0;
TArray<FCaptureDeviceInfo> CaptureDevicesInfo;
AudioCapture.GetCaptureDevicesAvailable(CaptureDevicesInfo);
uint32 NumDevices = CaptureDevicesInfo.Num();
Audio::FCaptureDeviceInfo DeviceInfo;
for (uint32 DeviceIndex = 0; AudioCapture.GetCaptureDeviceInfo(DeviceInfo, DeviceIndex); DeviceIndex++)
{
//CONVAI_LOG(ConvaiAudioLog, Log, TEXT("DeviceIndex: %d, InputDeviceID: %d - name: %s - channels:%d - NumDevices:%d"), DeviceIndex, InputDeviceID, *DeviceInfo.DeviceName, DeviceInfo.InputChannels, NumDevices);
bool IsInput = DeviceInfo.InputChannels > 0;
if (!IsInput)
{
continue;
}
//CONVAI_LOG(ConvaiAudioLog, Log, TEXT("DeviceIndex: %d, is an input - name: %s"), DeviceIndex, *DeviceInfo.DeviceName);
if (NumInputDevices == InputDeviceID)
{
return DeviceIndex;
}
NumInputDevices++;
}
return -1;
}
bool FConvaiAudioCaptureSynth::GetDefaultCaptureDeviceInfo(FCaptureDeviceInfo& OutInfo)
{
return AudioCapture.GetCaptureDeviceInfo(OutInfo);
}
bool FConvaiAudioCaptureSynth::GetCaptureDeviceInfo(FCaptureDeviceInfo& OutInfo, int32 DeviceIndex)
{
DeviceIndex = MapInputDeviceIDToDeviceID(DeviceIndex);
return AudioCapture.GetCaptureDeviceInfo(OutInfo, DeviceIndex);
}
bool FConvaiAudioCaptureSynth::OpenDefaultStream()
{
bool bSuccess = true;
if (!AudioCapture.IsStreamOpen())
{
FOnCaptureFunction OnCapture = [this](const float* AudioData, int32 NumFrames, int32 NumChannels, int32 SampleRate, double StreamTime, bool bOverFlow)
{
int32 NumSamples = NumChannels * NumFrames;
FScopeLock Lock(&CaptureCriticalSection);
if (bIsCapturing)
{
// Append the audio memory to the capture data buffer
int32 Index = AudioCaptureData.AddUninitialized(NumSamples);
float* AudioCaptureDataPtr = AudioCaptureData.GetData();
FMemory::Memcpy(&AudioCaptureDataPtr[Index], AudioData, NumSamples * sizeof(float));
}
};
// Prepare the audio buffer memory for 2 seconds of stereo audio at 48k SR to reduce chance for allocation in callbacks
AudioCaptureData.Reserve(2 * 2 * 48000);
FAudioCaptureDeviceParams Params = FAudioCaptureDeviceParams();
// Start the stream here to avoid hitching the audio render thread.
if (AudioCapture.OpenCaptureStream(Params, MoveTemp(OnCapture), 1024))
{
AudioCapture.StartStream();
}
else
{
bSuccess = false;
}
}
return bSuccess;
}
bool FConvaiAudioCaptureSynth::OpenStream(int32 DeviceIndex)
{
bool bSuccess = true;
if (!AudioCapture.IsStreamOpen())
{
FOnCaptureFunction OnCapture = [this](const float* AudioData, int32 NumFrames, int32 NumChannels, int32 SampleRate, double StreamTime, bool bOverFlow)
{
int32 NumSamples = NumChannels * NumFrames;
FScopeLock Lock(&CaptureCriticalSection);
if (bIsCapturing)
{
// Append the audio memory to the capture data buffer
int32 Index = AudioCaptureData.AddUninitialized(NumSamples);
float* AudioCaptureDataPtr = AudioCaptureData.GetData();
FMemory::Memcpy(&AudioCaptureDataPtr[Index], AudioData, NumSamples * sizeof(float));
}
};
// Prepare the audio buffer memory for 2 seconds of stereo audio at 48k SR to reduce chance for allocation in callbacks
AudioCaptureData.Reserve(2 * 2 * 48000);
FAudioCaptureDeviceParams Params = FAudioCaptureDeviceParams();
Params.DeviceIndex = DeviceIndex;
// Start the stream here to avoid hitching the audio render thread.
if (AudioCapture.OpenCaptureStream(Params, MoveTemp(OnCapture), 1024))
{
AudioCapture.StartStream();
}
else
{
bSuccess = false;
}
}
return bSuccess;
}
void FConvaiAudioCaptureSynth::CloseStream()
{
if (AudioCapture.IsStreamOpen())
{
AudioCapture.CloseStream();
}
}
bool FConvaiAudioCaptureSynth::StartCapturing()
{
FScopeLock Lock(&CaptureCriticalSection);
AudioCaptureData.Reset();
check(AudioCapture.IsStreamOpen());
bIsCapturing = true;
return true;
}
void FConvaiAudioCaptureSynth::StopCapturing()
{
check(AudioCapture.IsStreamOpen());
check(AudioCapture.IsCapturing());
FScopeLock Lock(&CaptureCriticalSection);
bIsCapturing = false;
}
void FConvaiAudioCaptureSynth::AbortCapturing()
{
AudioCapture.AbortStream();
AudioCapture.CloseStream();
}
bool FConvaiAudioCaptureSynth::IsStreamOpen() const
{
return AudioCapture.IsStreamOpen();
}
bool FConvaiAudioCaptureSynth::IsCapturing() const
{
return bIsCapturing;
}
int32 FConvaiAudioCaptureSynth::GetNumSamplesEnqueued()
{
FScopeLock Lock(&CaptureCriticalSection);
return AudioCaptureData.Num();
}
FAudioCapture* FConvaiAudioCaptureSynth::GetAudioCapture()
{
return &AudioCapture;
}
bool FConvaiAudioCaptureSynth::GetAudioData(TArray<float>& OutAudioData)
{
FScopeLock Lock(&CaptureCriticalSection);
int32 CaptureDataSamples = AudioCaptureData.Num();
if (CaptureDataSamples > 0)
{
// Append the capture audio to the output buffer
int32 OutIndex = OutAudioData.AddUninitialized(CaptureDataSamples);
float* OutDataPtr = OutAudioData.GetData();
FMemory::Memcpy(&OutDataPtr[OutIndex], AudioCaptureData.GetData(), CaptureDataSamples * sizeof(float));
// Reset the capture data buffer since we copied the audio out
AudioCaptureData.Reset();
return true;
}
return false;
}
};
UConvaiAudioCaptureComponent::UConvaiAudioCaptureComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bSuccessfullyInitialized = false;
bIsCapturing = false;
CapturedAudioDataSamples = 0;
ReadSampleIndex = 0;
bIsDestroying = false;
bIsNotReadyForForFinishDestroy = false;
bIsStreamOpen = false;
CaptureAudioData.Reserve(2 * 48000 * 5);
SelectedDeviceIndex = -1;
}
bool UConvaiAudioCaptureComponent::Init(int32& SampleRate)
{
Audio::FCaptureDeviceInfo DeviceInfo;
bool FoundDevice = false;
if (SelectedDeviceIndex == -1)
{
FoundDevice = CaptureSynth.GetDefaultCaptureDeviceInfo(DeviceInfo);
}
else
{
FoundDevice = CaptureSynth.GetCaptureDeviceInfo(DeviceInfo, SelectedDeviceIndex);
if (!FoundDevice)
{
FoundDevice = CaptureSynth.GetDefaultCaptureDeviceInfo(DeviceInfo);
}
}
CONVAI_LOG(ConvaiAudioLog, Log, TEXT("Using %s as Audio capture device with NumChannels:%d and SampleRate:%d"), *DeviceInfo.DeviceName, DeviceInfo.InputChannels, DeviceInfo.PreferredSampleRate);
if (FoundDevice)
{
SampleRate = DeviceInfo.PreferredSampleRate;
NumChannels = DeviceInfo.InputChannels;
// Only support mono and stereo mic inputs for now...
if (NumChannels == 1 || NumChannels == 2)
{
// This may fail if capture synths aren't supported on a given platform or if something went wrong with the capture device
bIsStreamOpen = CaptureSynth.OpenStream(SelectedDeviceIndex);
if (!bIsStreamOpen)
{
CONVAI_LOG(ConvaiAudioLog, Warning, TEXT("OpenStream returned false."));
}
return true;
}
else
{
CONVAI_LOG(ConvaiAudioLog, Warning, TEXT("Audio capture components only support mono and stereo mic input - Audio might be mangeled."));
return true;
}
}
return false;
}
void UConvaiAudioCaptureComponent::BeginDestroy()
{
Super::BeginDestroy();
// Flag that we're beginning to be destroyed
// This is so that if a mic capture is open, we shut it down on the render thread
bIsDestroying = true;
// Make sure stop is kicked off
Stop();
}
bool UConvaiAudioCaptureComponent::IsReadyForFinishDestroy()
{
return !bIsNotReadyForForFinishDestroy;
}
void UConvaiAudioCaptureComponent::FinishDestroy()
{
if (CaptureSynth.IsStreamOpen())
{
CaptureSynth.AbortCapturing();
}
check(!CaptureSynth.IsStreamOpen());
Super::FinishDestroy();
bSuccessfullyInitialized = false;
bIsCapturing = false;
bIsDestroying = false;
bIsStreamOpen = false;
}
bool UConvaiAudioCaptureComponent::GetDefaultCaptureDeviceInfo(Audio::FCaptureDeviceInfo& OutInfo)
{
return CaptureSynth.GetDefaultCaptureDeviceInfo(OutInfo);
}
bool UConvaiAudioCaptureComponent::GetCaptureDeviceInfo(Audio::FCaptureDeviceInfo& OutInfo, int32 DeviceIndex)
{
return CaptureSynth.GetCaptureDeviceInfo(OutInfo, DeviceIndex);
}
TArray<Audio::FCaptureDeviceInfo> UConvaiAudioCaptureComponent::GetCaptureDevicesAvailable()
{
return CaptureSynth.GetCaptureDevicesAvailable();
}
int32 UConvaiAudioCaptureComponent::GetActiveCaptureDevice(Audio::FCaptureDeviceInfo& OutInfo)
{
if (SelectedDeviceIndex == -1)
GetDefaultCaptureDeviceInfo(OutInfo);
else
GetCaptureDeviceInfo(OutInfo, SelectedDeviceIndex);
return SelectedDeviceIndex;
}
bool UConvaiAudioCaptureComponent::SetCaptureDevice(int32 DeviceIndex)
{
Audio::FCaptureDeviceInfo OutInfo;
if (GetCaptureDeviceInfo(OutInfo, DeviceIndex))
{
CaptureSynth.CloseStream();
SelectedDeviceIndex = DeviceIndex;
return true;
}
else
{
return false;
}
}
Audio::FConvaiAudioCaptureSynth* UConvaiAudioCaptureComponent::GetCaptureSynth()
{
return &CaptureSynth;
}
void UConvaiAudioCaptureComponent::OnBeginGenerate()
{
CapturedAudioDataSamples = 0;
ReadSampleIndex = 0;
CaptureAudioData.Reset();
if (!bIsStreamOpen)
{
bIsStreamOpen = CaptureSynth.OpenDefaultStream();
}
if (bIsStreamOpen)
{
CaptureSynth.StartCapturing();
check(CaptureSynth.IsCapturing());
// Don't allow this component to be destroyed until the stream is closed again
bIsNotReadyForForFinishDestroy = true;
FramesSinceStarting = 0;
ReadSampleIndex = 0;
}
}
void UConvaiAudioCaptureComponent::OnEndGenerate()
{
if (bIsStreamOpen)
{
check(CaptureSynth.IsStreamOpen());
CaptureSynth.StopCapturing();
bIsStreamOpen = false;
bIsNotReadyForForFinishDestroy = false;
}
}
int32 UConvaiAudioCaptureComponent::OnGenerateAudio(float* OutAudio, int32 NumSamples)
{
// Don't do anything if the stream isn't open
if (!bIsStreamOpen || !CaptureSynth.IsStreamOpen() || !CaptureSynth.IsCapturing())
{
// Just return NumSamples, which uses zero'd buffer
return NumSamples;
}
int32 OutputSamplesGenerated = 0;
if (CapturedAudioDataSamples > 0 || CaptureSynth.GetNumSamplesEnqueued() > 1024)
{
// Check if we need to read more audio data from capture synth
if (ReadSampleIndex + NumSamples > CaptureAudioData.Num())
{
// but before we do, copy off the remainder of the capture audio data buffer if there's data in it
int32 SamplesLeft = FMath::Max(0, CaptureAudioData.Num() - ReadSampleIndex);
if (SamplesLeft > 0)
{
float* CaptureDataPtr = CaptureAudioData.GetData();
FMemory::Memcpy(OutAudio, &CaptureDataPtr[ReadSampleIndex], SamplesLeft * sizeof(float));
// Track samples generated
OutputSamplesGenerated += SamplesLeft;
}
// Get another block of audio from the capture synth
CaptureAudioData.Reset();
CaptureSynth.GetAudioData(CaptureAudioData);
// Reset the read sample index since we got a new buffer of audio data
ReadSampleIndex = 0;
}
// note it's possible we didn't get any more audio in our last attempt to get it
if (CaptureAudioData.Num() > 0)
{
// Compute samples to copy
int32 NumSamplesToCopy = FMath::Min(NumSamples - OutputSamplesGenerated, CaptureAudioData.Num() - ReadSampleIndex);
float* CaptureDataPtr = CaptureAudioData.GetData();
FMemory::Memcpy(&OutAudio[OutputSamplesGenerated], &CaptureDataPtr[ReadSampleIndex], NumSamplesToCopy * sizeof(float));
ReadSampleIndex += NumSamplesToCopy;
OutputSamplesGenerated += NumSamplesToCopy;
}
CapturedAudioDataSamples += OutputSamplesGenerated;
}
else
{
// Say we generated the full samples, this will result in silence
OutputSamplesGenerated = NumSamples;
}
return OutputSamplesGenerated;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiDefinitions.h"
const TMap<EEmotionIntensity, float> FConvaiEmotionState::ScoreMultipliers =
{
{EEmotionIntensity::None, 0.0},
{EEmotionIntensity::LessIntense, 0.25},
{EEmotionIntensity::Basic, 0.6},
{EEmotionIntensity::MoreIntense, 1}
};

View File

@@ -0,0 +1,346 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiFaceSync.h"
#include "Misc/ScopeLock.h"
#include "ConvaiUtils.h"
DEFINE_LOG_CATEGORY(ConvaiFaceSyncLog);
namespace
{
// Helper function: Creates a zero blendshapes map
TMap<FName, float> CreateZeroBlendshapes()
{
TMap<FName, float> ZeroBlendshapes;
for (const auto& BlendShapeName : ConvaiConstants::BlendShapesNames)
{
ZeroBlendshapes.Add(*BlendShapeName, 0.0);
}
return ZeroBlendshapes;
}
// Helper function: Creates a zero visemes map
TMap<FName, float> CreateZeroVisemes()
{
TMap<FName, float> ZeroVisemes;
for (const auto& VisemeName : ConvaiConstants::VisemeNames)
{
ZeroVisemes.Add(*VisemeName, 0.0);
}
ZeroVisemes["sil"] = 1;
return ZeroVisemes;
}
float Calculate1DBezierCurve(float t, float P0, float P1, float P2, float P3)
{
// Make sure t is in the range [0, 1]
t = FMath::Clamp(t, 0.0f, 1.0f);
// Calculate the 1D Bezier curve value at time t
float result = FMath::Pow(1.0f - t, 3.0f) * P0 + 3.0f * FMath::Pow(1.0f - t, 2.0f) * t * P1 +
3.0f * (1.0f - t) * FMath::Pow(t, 2.0f) * P2 + FMath::Pow(t, 3.0f) * P3;
return result;
}
};
const TMap<FName, float> UConvaiFaceSyncComponent::ZeroBlendshapeFrame = CreateZeroBlendshapes();
const TMap<FName, float> UConvaiFaceSyncComponent::ZeroVisemeFrame = CreateZeroVisemes();
UConvaiFaceSyncComponent::UConvaiFaceSyncComponent()
{
PrimaryComponentTick.bCanEverTick = true;
CurrentSequenceTimePassed = 0;
bAutoActivate = true;
//CurrentBlendShapesMap = ZeroBlendshapeFrame;
}
UConvaiFaceSyncComponent::~UConvaiFaceSyncComponent() // Implement destructor
{
}
void UConvaiFaceSyncComponent::BeginPlay()
{
Super::BeginPlay();
CurrentBlendShapesMap = GenerateZeroFrame();
}
void UConvaiFaceSyncComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Interpolate blendshapes and advance animation sequence
if (IsValidSequence(MainSequenceBuffer))
{
SequenceCriticalSection.Lock();
// Calculate time passed since ConvaiProcessLipSyncAdvanced was called
double CurrentTime = FPlatformTime::Seconds();
CurrentSequenceTimePassed = CurrentTime - StartTime;
if (CurrentSequenceTimePassed > MainSequenceBuffer.Duration)
{
ConvaiStopLipSync();
SequenceCriticalSection.Unlock();
return;
}
bIsPlaying = true;
// Calculate frame duration and offsets
float FrameDuration = MainSequenceBuffer.Duration / MainSequenceBuffer.AnimationFrames.Num();
float FrameOffset = FrameDuration * 0.5f;
int32 FrameIndex;
int32 BufferIndex;
TMap<FName, float> StartFrame;
TMap<FName, float> EndFrame;
float Alpha;
// Choose the current and next BlendShapes
if (CurrentSequenceTimePassed <= FrameOffset)
{
//StartFrame = ZeroBlendshapeFrame;
StartFrame = GetCurrentFrame();
EndFrame = MainSequenceBuffer.AnimationFrames[0].BlendShapes;
Alpha = CurrentSequenceTimePassed / FrameOffset + 0.5;
FrameIndex = MainSequenceBuffer.AnimationFrames[0].FrameIndex;
BufferIndex = 0;
}
else if (CurrentSequenceTimePassed >= MainSequenceBuffer.Duration - FrameOffset)
{
int LastFrameIdx = MainSequenceBuffer.AnimationFrames.Num() - 1;
StartFrame = MainSequenceBuffer.AnimationFrames[LastFrameIdx].BlendShapes;
EndFrame = GenerateZeroFrame();
Alpha = (CurrentSequenceTimePassed - (MainSequenceBuffer.Duration - FrameOffset)) / FrameOffset;
FrameIndex = MainSequenceBuffer.AnimationFrames[LastFrameIdx].FrameIndex;
BufferIndex = LastFrameIdx;
}
else
{
int CurrentFrameIndex = FMath::FloorToInt((CurrentSequenceTimePassed - FrameOffset) / FrameDuration);
CurrentFrameIndex = FMath::Min(CurrentFrameIndex, MainSequenceBuffer.AnimationFrames.Num() - 1);
int NextFrameIndex = FMath::Min(CurrentFrameIndex + 1, MainSequenceBuffer.AnimationFrames.Num() - 1);
StartFrame = MainSequenceBuffer.AnimationFrames[CurrentFrameIndex].BlendShapes;
EndFrame = MainSequenceBuffer.AnimationFrames[NextFrameIndex].BlendShapes;
Alpha = (CurrentSequenceTimePassed - FrameOffset - (CurrentFrameIndex * FrameDuration)) / FrameDuration;
Apply_StartEndFrames_PostProcessing(CurrentFrameIndex, NextFrameIndex, Alpha, StartFrame, EndFrame);
FrameIndex = MainSequenceBuffer.AnimationFrames[CurrentFrameIndex].FrameIndex;
BufferIndex = CurrentFrameIndex;
}
SequenceCriticalSection.Unlock();
// CONVAI_LOG(ConvaiFaceSyncLog, Log, TEXT("Evaluate: FrameIndex:%d Alpha: %f FramesLeft: %d"), FrameIndex, Alpha, MainSequenceBuffer.AnimationFrames.Num() - BufferIndex);
//CurrentBlendShapesMap = StartFrame;
CurrentBlendShapesMap = InterpolateFrames(StartFrame, EndFrame, Alpha);
ApplyPostProcessing();
// Trigger the blueprint event
OnVisemesDataReady.ExecuteIfBound();
}
}
void UConvaiFaceSyncComponent::ConvaiApplyPrecomputedFacialAnimation(uint8* InPCMData, uint32 InPCMDataSize, uint32 InSampleRate, uint32 InNumChannels, FAnimationSequence FaceSequence)
{
SequenceCriticalSection.Lock();
MainSequenceBuffer.AnimationFrames.Append(FaceSequence.AnimationFrames);
MainSequenceBuffer.Duration += FaceSequence.Duration;
MainSequenceBuffer.FrameRate = FaceSequence.FrameRate;
CalculateStartingTime();
SequenceCriticalSection.Unlock();
if (IsRecordingLipSync)
{
FScopeLock ScopeLock(&RecordingCriticalSection);
RecordedSequenceBuffer.AnimationFrames.Append(FaceSequence.AnimationFrames);
RecordedSequenceBuffer.Duration += FaceSequence.Duration;
RecordedSequenceBuffer.FrameRate = FaceSequence.FrameRate;
}
}
void UConvaiFaceSyncComponent::ConvaiApplyFacialFrame(FAnimationFrame FaceFrame, float Duration)
{
SequenceCriticalSection.Lock();
if (!GeneratesVisemesAsBlendshapes())
{
float* sil = FaceFrame.BlendShapes.Find("sil");
if (sil != nullptr && *sil < 0)
{
ClearMainSequence();
Stopping = true;
return;
}
}
MainSequenceBuffer.AnimationFrames.Add(FaceFrame);
MainSequenceBuffer.Duration += Duration;
MainSequenceBuffer.FrameRate = Duration==0? -1 : 1.0/Duration;
SequenceCriticalSection.Unlock();
if (IsRecordingLipSync)
{
FScopeLock ScopeLock(&RecordingCriticalSection);
RecordedSequenceBuffer.AnimationFrames.Add(FaceFrame);
RecordedSequenceBuffer.Duration += Duration;
RecordedSequenceBuffer.FrameRate = Duration == 0 ? -1 : 1.0 / Duration;
}
}
void UConvaiFaceSyncComponent::StartRecordingLipSync()
{
if (IsRecordingLipSync)
{
CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("Cannot start Recording LipSync while already recording LipSync"));
return;
}
CONVAI_LOG(ConvaiFaceSyncLog, Log, TEXT("Started Recording LipSync"));
IsRecordingLipSync = true;
}
FAnimationSequenceBP UConvaiFaceSyncComponent::FinishRecordingLipSync()
{
CONVAI_LOG(ConvaiFaceSyncLog, Log, TEXT("Finished Recording LipSync - Total Frames: %d - Duration: %f"), RecordedSequenceBuffer.AnimationFrames.Num(), RecordedSequenceBuffer.Duration);
if (!IsRecordingLipSync)
return FAnimationSequenceBP();
FScopeLock ScopeLock(&RecordingCriticalSection);
IsRecordingLipSync = false;
FAnimationSequenceBP AnimationSequenceBP;
AnimationSequenceBP.AnimationSequence = RecordedSequenceBuffer;
RecordedSequenceBuffer.AnimationFrames.Empty();
RecordedSequenceBuffer.Duration = 0;
return AnimationSequenceBP;
}
bool UConvaiFaceSyncComponent::PlayRecordedLipSync(FAnimationSequenceBP RecordedLipSync, int StartFrame, int EndFrame, float OverwriteDuration)
{
if (!IsValidSequence(RecordedLipSync.AnimationSequence))
{
CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("Recorded LipSync is not valid - Total Frames: %d - Duration: %f"), RecordedLipSync.AnimationSequence.AnimationFrames.Num(), RecordedLipSync.AnimationSequence.Duration);
return false;
}
if (IsRecordingLipSync)
{
CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("Cannot Play Recorded LipSync while Recording LipSync"));
return false;
}
if (IsValidSequence(MainSequenceBuffer))
{
CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("Playing Recorded LipSync and stopping currently playing LipSync"));
ConvaiStopLipSync();
}
if (StartFrame > 0 && StartFrame > RecordedLipSync.AnimationSequence.AnimationFrames.Num() - 1)
{
CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("StartFrame is greater than the recorded LipSync - StartFrame: %d Total Frames: %d - Duration: %f"), StartFrame, RecordedLipSync.AnimationSequence.AnimationFrames.Num(), RecordedLipSync.AnimationSequence.Duration);
return false;
}
if (EndFrame > 0 && EndFrame < StartFrame)
{
CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("StartFrame cannot be greater than the EndFrame"));
return false;
}
//if (EndFrame > 0 && EndFrame > RecordedLipSync.AnimationSequence.AnimationFrames.Num() - 1)
//{
// CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("EndFrame is greater than the recorded LipSync - EndFrame: %d Total Frames: %d - Duration: %f"), EndFrame, RecordedLipSync.AnimationSequence.AnimationFrames.Num(), RecordedLipSync.AnimationSequence.Duration);
// return false;
//}
if (EndFrame > 0 && EndFrame < RecordedLipSync.AnimationSequence.AnimationFrames.Num()-1)
{
float FrameDuration = RecordedLipSync.AnimationSequence.Duration / RecordedLipSync.AnimationSequence.AnimationFrames.Num();
int NumRemovedFrames = RecordedLipSync.AnimationSequence.AnimationFrames.Num() - EndFrame - 1;
RecordedLipSync.AnimationSequence.AnimationFrames.RemoveAt(EndFrame + 1, NumRemovedFrames);
float RemovedDuration = NumRemovedFrames * FrameDuration;
RecordedLipSync.AnimationSequence.Duration -= RemovedDuration;
}
if (StartFrame > 0)
{
float FrameDuration = RecordedLipSync.AnimationSequence.Duration / RecordedLipSync.AnimationSequence.AnimationFrames.Num();
int NumRemovedFrames = StartFrame;
RecordedLipSync.AnimationSequence.AnimationFrames.RemoveAt(0, NumRemovedFrames);
float RemovedDuration = NumRemovedFrames * FrameDuration;
RecordedLipSync.AnimationSequence.Duration -= RemovedDuration;
}
if (OverwriteDuration > 0)
{
RecordedLipSync.AnimationSequence.Duration = OverwriteDuration;
}
CONVAI_LOG(ConvaiFaceSyncLog, Log, TEXT("Playing Recorded LipSync - Total Frames: %d - Duration: %f"), RecordedLipSync.AnimationSequence.AnimationFrames.Num(), RecordedLipSync.AnimationSequence.Duration);
ConvaiApplyPrecomputedFacialAnimation(nullptr, 0, 0, 0, RecordedLipSync.AnimationSequence);
return true;
}
bool UConvaiFaceSyncComponent::IsValidSequence(const FAnimationSequence& Sequence)
{
if (Sequence.Duration > 0 && Sequence.AnimationFrames.Num() > 0 && Sequence.AnimationFrames[0].BlendShapes.Num() > 0)
return true;
else
return false;
}
bool UConvaiFaceSyncComponent::IsPlaying()
{
return bIsPlaying;
}
void UConvaiFaceSyncComponent::CalculateStartingTime()
{
if (!bIsPlaying)
StartTime = FPlatformTime::Seconds();
}
void UConvaiFaceSyncComponent::ForceRecalculateStartTime()
{
StartTime = FPlatformTime::Seconds();
}
void UConvaiFaceSyncComponent::ClearMainSequence()
{
SequenceCriticalSection.Lock();
MainSequenceBuffer.AnimationFrames.Empty();
MainSequenceBuffer.Duration = 0;
SequenceCriticalSection.Unlock();
}
TMap<FName, float> UConvaiFaceSyncComponent::InterpolateFrames(const TMap<FName, float>& StartFrame, const TMap<FName, float>& EndFrame, float Alpha)
{
const TArray<FString>& CurveNames = GeneratesVisemesAsBlendshapes() ? ConvaiConstants::BlendShapesNames : ConvaiConstants::VisemeNames;
TMap<FName, float> Result;
for (const auto& CurveName : CurveNames)
{
float StartValue = StartFrame.Contains(*CurveName) ? StartFrame[*CurveName] : 0.0;
float EndValue = EndFrame.Contains(*CurveName) ? EndFrame[*CurveName] : 0.0;
Result.Add(*CurveName, FMath::Lerp(StartValue, EndValue, Alpha));
}
return Result;
}
void UConvaiFaceSyncComponent::ConvaiStopLipSync()
{
bIsPlaying = false;
CurrentSequenceTimePassed = 0;
ClearMainSequence();
SetCurrentFrametoZero();
OnVisemesDataReady.ExecuteIfBound();
// CONVAI_LOG(ConvaiFaceSyncLog, Warning, TEXT("Stopping LipSync"));
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,256 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiNarrativeDesignProxy.h"
#include "ConvaiUtils.h"
#include "../Convai.h"
#include "Engine.h"
#include "RestAPI/ConvaiURL.h"
DEFINE_LOG_CATEGORY(ConvaiNarrativeHTTP);
UFetchNarrativeSectionsProxy* UFetchNarrativeSectionsProxy::FetchNarrativeSections(UObject* WorldContextObject, FString InCharacterId)
{
UFetchNarrativeSectionsProxy* Proxy = NewObject<UFetchNarrativeSectionsProxy>();
Proxy->WorldPtr = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
Proxy->CharacterId = InCharacterId;
return Proxy;
}
void UFetchNarrativeSectionsProxy::Activate()
{
UWorld* World = WorldPtr.Get();
if (!World)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("Could not get a pointer to world!"));
failed();
return;
}
FHttpModule* Http = &FHttpModule::Get();
if (!Http)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("Could not get a pointer to http module!"));
failed();
return;
}
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
// Form Validation
if (
!UConvaiFormValidation::ValidateAuthKey(AuthKey)
|| !UConvaiFormValidation::ValidateCharacterID(CharacterId)
)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("UFetchNarrativeSectionsProxy::Activate Invalid Character or API key"));
failed();
return;
}
FHttpModule* HttpModule = &FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest = HttpModule->CreateRequest();
HttpRequest->OnProcessRequestComplete().BindUObject(this, &UFetchNarrativeSectionsProxy::OnHttpRequestCompleted);
HttpRequest->SetURL(UConvaiURL::GetFullURL(TEXT("character/narrative/list-sections"), false));
HttpRequest->SetVerb(TEXT("POST"));
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
HttpRequest->SetHeader(AuthHeader, AuthKey);
HttpRequest->SetContentAsString(FString::Printf(TEXT("{\"character_id\": \"%s\"}"), *CharacterId));
HttpRequest->ProcessRequest();
}
void UFetchNarrativeSectionsProxy::OnHttpRequestCompleted(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
NarrativeSections.Empty();
if (!Response)
{
if (bWasSuccessful)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("HTTP request succeded - But response pointer is invalid"));
}
else
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("HTTP request failed - Response pointer is invalid"));
}
failed();
return;
}
if (!bWasSuccessful || Response->GetResponseCode() < 200 || Response->GetResponseCode() > 299)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("HTTP request failed with code %d"), Response->GetResponseCode());
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("Response:%s"), *Response->GetContentAsString());
failed();
return;
}
TArray<TSharedPtr<FJsonValue>> JsonArray;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonArray)) // Deserialize into a JSON array
{
for (const TSharedPtr<FJsonValue>& Val : JsonArray)
{
FNarrativeSection Section;
if (FJsonObjectConverter::JsonObjectToUStruct(Val->AsObject().ToSharedRef(), FNarrativeSection::StaticStruct(), &Section))
{
NarrativeSections.Add(Section);
}
}
success();
return;
}
}
void UFetchNarrativeSectionsProxy::failed()
{
//auto InvocationList = OnFailure.GetAllObjects();
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnFailure delegate is bound to %d functions before invokation."), InvocationList.Num());
OnFailure.Broadcast(NarrativeSections);
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnFailure delegate is bound to %d functions after invokation."), InvocationList.Num());
finish();
}
void UFetchNarrativeSectionsProxy::success()
{
//auto InvocationList = OnSuccess.GetAllObjects();
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnSuccess delegate is bound to %d functions before invokation."), InvocationList.Num());
OnSuccess.Broadcast(NarrativeSections);
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnSuccess delegate is bound to %d functions after invokation."), InvocationList.Num());
finish();
}
void UFetchNarrativeSectionsProxy::finish()
{
}
UFetchNarrativeTriggersProxy* UFetchNarrativeTriggersProxy::FetchNarrativeTriggers(UObject* WorldContextObject, FString InCharacterId)
{
UFetchNarrativeTriggersProxy* Proxy = NewObject<UFetchNarrativeTriggersProxy>();
Proxy->WorldPtr = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
Proxy->CharacterId = InCharacterId;
return Proxy;
}
void UFetchNarrativeTriggersProxy::Activate()
{
UWorld* World = WorldPtr.Get();
if (!World)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("Could not get a pointer to world!"));
failed();
return;
}
FHttpModule* Http = &FHttpModule::Get();
if (!Http)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("Could not get a pointer to http module!"));
failed();
return;
}
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
// Form Validation
if (
!UConvaiFormValidation::ValidateAuthKey(AuthKey)
|| !UConvaiFormValidation::ValidateCharacterID(CharacterId)
)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("UFetchNarrativeTriggersProxy::Activate Invalid Character or API key"));
failed();
return;
}
FHttpModule* HttpModule = &FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest = HttpModule->CreateRequest();
HttpRequest->OnProcessRequestComplete().BindUObject(this, &UFetchNarrativeTriggersProxy::OnHttpRequestCompleted);
HttpRequest->SetURL(UConvaiURL::GetFullURL(TEXT("character/narrative/list-triggers"), false));
HttpRequest->SetVerb(TEXT("POST"));
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
HttpRequest->SetHeader(AuthHeader, AuthKey);
HttpRequest->SetContentAsString(FString::Printf(TEXT("{\"character_id\": \"%s\"}"), *CharacterId));
HttpRequest->ProcessRequest();
}
void UFetchNarrativeTriggersProxy::OnHttpRequestCompleted(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
NarrativeTriggers.Empty();
if (!Response)
{
if (bWasSuccessful)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("HTTP request succeded - But response pointer is invalid"));
}
else
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("HTTP request failed - Response pointer is invalid"));
}
failed();
return;
}
if (!bWasSuccessful || Response->GetResponseCode() < 200 || Response->GetResponseCode() > 299)
{
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("HTTP request failed with code %d"), Response->GetResponseCode());
CONVAI_LOG(ConvaiNarrativeHTTP, Warning, TEXT("Response:%s"), *Response->GetContentAsString());
failed();
return;
}
TArray<TSharedPtr<FJsonValue>> JsonArray;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonArray)) // Deserialize into a JSON array
{
for (const TSharedPtr<FJsonValue>& Val : JsonArray)
{
FNarrativeTrigger Trigger;
if (FJsonObjectConverter::JsonObjectToUStruct(Val->AsObject().ToSharedRef(), FNarrativeTrigger::StaticStruct(), &Trigger))
{
NarrativeTriggers.Add(Trigger);
}
}
success();
return;
}
}
void UFetchNarrativeTriggersProxy::failed()
{
//auto InvocationList = OnFailure.GetAllObjects();
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnFailure delegate is bound to %d functions before invokation."), InvocationList.Num());
OnFailure.Broadcast(NarrativeTriggers);
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnFailure delegate is bound to %d functions after invokation."), InvocationList.Num());
finish();
}
void UFetchNarrativeTriggersProxy::success()
{
//auto InvocationList = OnSuccess.GetAllObjects();
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnSuccess delegate is bound to %d functions before invokation."), InvocationList.Num());
OnSuccess.Broadcast(NarrativeTriggers);
//CONVAI_LOG(ConvaiNarrativeHTTP, Log, TEXT("OnSuccess delegate is bound to %d functions after invokation."), InvocationList.Num());
finish();
}
void UFetchNarrativeTriggersProxy::finish()
{
}

View File

@@ -0,0 +1,984 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiPlayerComponent.h"
#include "ConvaiAudioCaptureComponent.h"
#include "ConvaiChatbotComponent.h"
#include "ConvaiActionUtils.h"
#include "ConvaiUtils.h"
#include "ConvaiDefinitions.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Net/UnrealNetwork.h"
#include "Misc/FileHelper.h"
#include "Http.h"
#include "ConvaiUtils.h"
#include "Containers/UnrealString.h"
#include "Kismet/GameplayStatics.h"
#include "AudioMixerBlueprintLibrary.h"
#include "Engine/GameEngine.h"
#include "Sound/SoundWave.h"
#include "AudioDevice.h"
#include "AudioMixerDevice.h"
#include "UObject/ConstructorHelpers.h"
#include "ConvaiSubsystem.h"
#include "Engine/GameInstance.h"
DEFINE_LOG_CATEGORY(ConvaiPlayerLog);
static FAudioDevice* GetAudioDeviceFromWorldContext(const UObject* WorldContextObject)
{
UWorld* ThisWorld = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
if (!ThisWorld || !ThisWorld->bAllowAudioPlayback || ThisWorld->GetNetMode() == NM_DedicatedServer)
{
return nullptr;
}
return ThisWorld->GetAudioDevice().GetAudioDevice();
}
static Audio::FMixerDevice* GetAudioMixerDeviceFromWorldContext(const UObject* WorldContextObject)
{
if (FAudioDevice* AudioDevice = GetAudioDeviceFromWorldContext(WorldContextObject))
{
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
bool Found = AudioDevice != nullptr;
#else
bool Found = AudioDevice != nullptr && AudioDevice->IsAudioMixerEnabled();
#endif
if (!Found)
{
return nullptr;
}
else
{
return static_cast<Audio::FMixerDevice*>(AudioDevice);
}
}
return nullptr;
}
UConvaiPlayerComponent::UConvaiPlayerComponent()
{
PrimaryComponentTick.bCanEverTick = true;
//SetIsReplicated(true);
bAutoActivate = true;
PlayerName = "Guest";
IsInit = false;
VoiceCaptureRingBuffer.Init(ConvaiConstants::VoiceCaptureRingBufferCapacity);
VoiceCaptureBuffer.Empty(ConvaiConstants::VoiceCaptureBufferSize);
UsePixelStreamingMicInput = true;
const FString FoundSubmixPath = "/ConvAI/Submixes/AudioInput.AudioInput";
static ConstructorHelpers::FObjectFinder<USoundSubmixBase> SoundSubmixFinder(*FoundSubmixPath);
if (SoundSubmixFinder.Succeeded())
{
_FoundSubmix = SoundSubmixFinder.Object;
}
}
void UConvaiPlayerComponent::OnComponentCreated()
{
Super::OnComponentCreated();
AudioCaptureComponent = NewObject<UConvaiAudioCaptureComponent>(this, UConvaiAudioCaptureComponent::StaticClass(), TEXT("ConvaiAudioCapture"));
if (AudioCaptureComponent)
{
AudioCaptureComponent->RegisterComponent();
}
//AudioCaptureComponent->SetupAttachment(this);
if (_FoundSubmix != nullptr) {
AudioCaptureComponent->SoundSubmix = _FoundSubmix;
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("UConvaiPlayerComponent: Found submix \"AudioInput\""));
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("UConvaiPlayerComponent: Audio Submix was not found, please ensure an audio submix exists at this directory: \"/ConvAI/Submixes/AudioInput\" then restart Unreal Engine"));
}
}
void UConvaiPlayerComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UConvaiPlayerComponent, PlayerName);
}
bool UConvaiPlayerComponent::Init()
{
if (IsInit)
{
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("AudioCaptureComponent is already init"));
return true;
}
FString commandMicNoiseGateThreshold = "voice.MicNoiseGateThreshold 0.01";
FString commandSilenceDetectionThreshold = "voice.SilenceDetectionThreshold 0.001";
APlayerController* PController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (PController)
{
PController->ConsoleCommand(*commandMicNoiseGateThreshold, true);
PController->ConsoleCommand(*commandSilenceDetectionThreshold, true);
}
AudioCaptureComponent = Cast<UConvaiAudioCaptureComponent>(GetOwner()->GetComponentByClass(UConvaiAudioCaptureComponent::StaticClass()));
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("Init: AudioCaptureComponent is not valid"));
return false;
}
IsInit = true;
Token = 0;
return true;
}
void UConvaiPlayerComponent::SetPlayerName(FString NewPlayerName)
{
PlayerName = NewPlayerName;
if (GetIsReplicated())
{
SetPlayerNameServer(PlayerName);
}
}
void UConvaiPlayerComponent::SetPlayerNameServer_Implementation(const FString& NewPlayerName)
{
PlayerName = NewPlayerName;
}
void UConvaiPlayerComponent::SetSpeakerID(FString NewSpeakerID)
{
SpeakerID = NewSpeakerID;
if (GetIsReplicated())
{
SetSpeakerIDServer(SpeakerID);
}
}
void UConvaiPlayerComponent::SetSpeakerIDServer_Implementation(const FString& NewSpeakerID)
{
SpeakerID = NewSpeakerID;
}
bool UConvaiPlayerComponent::GetDefaultCaptureDeviceInfo(FCaptureDeviceInfoBP& OutInfo)
{
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetDefaultCaptureDeviceInfo: AudioCaptureComponent is not valid"));
return false;
}
return false;
}
bool UConvaiPlayerComponent::GetCaptureDeviceInfo(FCaptureDeviceInfoBP& OutInfo, int DeviceIndex)
{
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetCaptureDeviceInfo: AudioCaptureComponent is not valid"));
return false;
}
Audio::FCaptureDeviceInfo OutDeviceInfo;
if (AudioCaptureComponent->GetCaptureDeviceInfo(OutDeviceInfo, DeviceIndex))
{
OutInfo.bSupportsHardwareAEC = OutDeviceInfo.bSupportsHardwareAEC;
OutInfo.LongDeviceId = OutDeviceInfo.DeviceId;
OutInfo.DeviceName = OutDeviceInfo.DeviceName;
OutInfo.InputChannels = OutDeviceInfo.InputChannels;
OutInfo.PreferredSampleRate = OutDeviceInfo.PreferredSampleRate;
OutInfo.DeviceIndex = DeviceIndex;
return true;
}
return false;
}
TArray<FCaptureDeviceInfoBP> UConvaiPlayerComponent::GetAvailableCaptureDeviceDetails()
{
TArray<FCaptureDeviceInfoBP> FCaptureDevicesInfoBP;
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetAvailableCaptureDeviceDetails: AudioCaptureComponent is not valid"));
return FCaptureDevicesInfoBP;
}
int i = 0;
for (auto DeviceInfo : AudioCaptureComponent->GetCaptureDevicesAvailable())
{
FCaptureDeviceInfoBP CaptureDeviceInfoBP;
CaptureDeviceInfoBP.bSupportsHardwareAEC = DeviceInfo.bSupportsHardwareAEC;
CaptureDeviceInfoBP.LongDeviceId = DeviceInfo.DeviceId;
CaptureDeviceInfoBP.DeviceName = DeviceInfo.DeviceName;
CaptureDeviceInfoBP.InputChannels = DeviceInfo.InputChannels;
CaptureDeviceInfoBP.PreferredSampleRate = DeviceInfo.PreferredSampleRate;
CaptureDeviceInfoBP.DeviceIndex = i++;
FCaptureDevicesInfoBP.Add(CaptureDeviceInfoBP);
}
return FCaptureDevicesInfoBP;
}
TArray<FString> UConvaiPlayerComponent::GetAvailableCaptureDeviceNames()
{
TArray<FString> AvailableDeviceNames;
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetAvailableCaptureDeviceNames: AudioCaptureComponent is not valid"));
return AvailableDeviceNames;
}
for (auto CaptureDeviceInfo : GetAvailableCaptureDeviceDetails())
{
AvailableDeviceNames.Add(CaptureDeviceInfo.DeviceName);
}
return AvailableDeviceNames;
}
void UConvaiPlayerComponent::GetActiveCaptureDevice(FCaptureDeviceInfoBP& OutInfo)
{
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetActiveCaptureDevice: AudioCaptureComponent is not valid"));
return;
}
Audio::FCaptureDeviceInfo OutDeviceInfo;
int SelectedDeviceIndex = AudioCaptureComponent->GetActiveCaptureDevice(OutDeviceInfo);
OutInfo.bSupportsHardwareAEC = OutDeviceInfo.bSupportsHardwareAEC;
OutInfo.LongDeviceId = OutDeviceInfo.DeviceId;
OutInfo.DeviceName = OutDeviceInfo.DeviceName;
OutInfo.InputChannels = OutDeviceInfo.InputChannels;
OutInfo.PreferredSampleRate = OutDeviceInfo.PreferredSampleRate;
OutInfo.DeviceIndex = SelectedDeviceIndex;
}
bool UConvaiPlayerComponent::SetCaptureDeviceByIndex(int DeviceIndex)
{
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetCaptureDeviceByIndex: AudioCaptureComponent is not valid"));
return false;
}
if (DeviceIndex >= GetAvailableCaptureDeviceDetails().Num())
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetCaptureDeviceByIndex: Invalid Device Index: %d - Max possible index: %d."), DeviceIndex, GetAvailableCaptureDeviceDetails().Num() - 1);
return false;
}
return AudioCaptureComponent->SetCaptureDevice(DeviceIndex);
}
bool UConvaiPlayerComponent::SetCaptureDeviceByName(FString DeviceName)
{
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetCaptureDeviceByName: AudioCaptureComponent is not valid"));
return false;
}
bool bDeviceFound = false;
int DeviceIndex = -1;
for (auto CaptureDeviceInfo : GetAvailableCaptureDeviceDetails())
{
if (CaptureDeviceInfo.DeviceName == DeviceName)
{
bDeviceFound = true;
DeviceIndex = CaptureDeviceInfo.DeviceIndex;
}
}
TArray<FString> AvailableDeviceNames;
if (!bDeviceFound)
{
AvailableDeviceNames = GetAvailableCaptureDeviceNames();
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetCaptureDeviceByName: Could not find Device name: %s - Available Device names are: [%s]."), *DeviceName, *FString::Join(AvailableDeviceNames, *FString(" - ")));
return false;
}
if (!SetCaptureDeviceByIndex(DeviceIndex))
{
AvailableDeviceNames = GetAvailableCaptureDeviceNames();
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetCaptureDeviceByName: SetCaptureDeviceByIndex failed for index: %d and device name: %s - Available Device names are: [%s]."), DeviceIndex, *DeviceName, *FString::Join(AvailableDeviceNames, *FString(" - ")));
return false;
}
return true;
}
void UConvaiPlayerComponent::SetMicrophoneVolumeMultiplier(float InVolumeMultiplier, bool& Success)
{
Success = false;
if (IsValid(AudioCaptureComponent))
{
AudioCaptureComponent->SetVolumeMultiplier(InVolumeMultiplier);
Success = true;
}
if (PixelStreamingAudioComponent.IsValid())
{
AudioCaptureComponent->SetVolumeMultiplier(InVolumeMultiplier*2); // multiply by 2 because Pixel Streaming mic is usually very low
Success = true;
}
if (!IsValid(AudioCaptureComponent) && !PixelStreamingAudioComponent.IsValid())
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetMicrophoneVolumeMultiplier: AudioCaptureComponent and PixelStreamingAudioComponent are not valid"));
}
}
void UConvaiPlayerComponent::GetMicrophoneVolumeMultiplier(float& OutVolumeMultiplier, bool& Success)
{
Success = false;
if (!IsValid(AudioCaptureComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SetMicrophoneVolumeMultiplier: AudioCaptureComponent is not valid"));
return;
}
auto InternalAudioComponent = AudioCaptureComponent->GetAudioComponent();
if (!InternalAudioComponent)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetMicrophoneVolumeMultiplier: InternalAudioComponent is not valid"));
}
OutVolumeMultiplier = InternalAudioComponent->VolumeMultiplier;
Success = true;
}
// void UConvaiPlayerComponent::GetIfHardwareFeatureIsSupported(EHardwareInputFeatureBP FeatureType, bool& Success)
// {
// Success = false;
// if (!IsValid(AudioCaptureComponent))
// {
// CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetIfHardwareFeatureIsSupported: AudioCaptureComponent is not valid"));
// return;
// }
// auto CaptureSynth = AudioCaptureComponent->GetCaptureSynth();
// if (CaptureSynth == nullptr)
// {
// CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetIfHardwareFeatureIsSupported: CaptureSynth is not valid"));
// return;
// }
// auto AudioCapture = CaptureSynth->GetAudioCapture();
// if (AudioCapture == nullptr)
// {
// CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("GetIfHardwareFeatureIsSupported: AudioCapture is not valid"));
// return;
// }
// Success = AudioCapture->GetIfHardwareFeatureIsSupported((Audio::EHardwareInputFeature)FeatureType);
// }
// void UConvaiPlayerComponent::SetHardwareFeatureEnabled(EHardwareInputFeatureBP FeatureType, bool bIsEnabled, bool& Success)
// {
// bool HardwareFeatureIsSupported;
// GetIfHardwareFeatureIsSupported(FeatureType, HardwareFeatureIsSupported);
// if (!HardwareFeatureIsSupported)
// {
// if (!bIsEnabled)
// {
// Success = true;
// return;
// }
// else
// {
// Success = false;
// return;
// }
// }
// auto AudioCapture = AudioCaptureComponent->GetCaptureSynth()->GetAudioCapture();
// AudioCapture->SetHardwareFeatureEnabled((Audio::EHardwareInputFeature)FeatureType, bIsEnabled);
// Success = true;
// }
void UConvaiPlayerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!IsInit || !IsValid(AudioCaptureComponent))
{
return;
}
UpdateVoiceCapture(DeltaTime);
}
void UConvaiPlayerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// Unregister from the ConvaiSubsystem
if (UGameInstance* GameInstance = GetWorld()->GetGameInstance())
{
if (UConvaiSubsystem* ConvaiSubsystem = GameInstance->GetSubsystem<UConvaiSubsystem>())
{
if (IsValid(ConvaiSubsystem))
{
ConvaiSubsystem->UnregisterPlayerComponent(this);
}
}
}
if (IsRecording)
FinishRecording();
if (IsStreaming)
FinishTalking();
Super::EndPlay(EndPlayReason);
}
bool UConvaiPlayerComponent::IsPixelStreamingEnabledAndAllowed()
{
return UsePixelStreamingMicInput && PixelStreamingAudioComponent.IsValid();
}
void UConvaiPlayerComponent::UpdateVoiceCapture(float DeltaTime)
{
if (IsRecording || IsStreaming) {
RemainingTimeUntilNextUpdate -= DeltaTime;
if (RemainingTimeUntilNextUpdate <= 0)
{
float ExpectedRecordingTime = DeltaTime > TIME_BETWEEN_VOICE_UPDATES_SECS ? DeltaTime : TIME_BETWEEN_VOICE_UPDATES_SECS;
FAudioThread::RunCommandOnAudioThread([this, ExpectedRecordingTime]()
{
StopVoiceChunkCapture();
StartVoiceChunkCapture(ExpectedRecordingTime);
});
RemainingTimeUntilNextUpdate = TIME_BETWEEN_VOICE_UPDATES_SECS;
}
}
else
{
RemainingTimeUntilNextUpdate = 0;
}
}
void UConvaiPlayerComponent::StartVoiceChunkCapture(float ExpectedRecordingTime)
{
//GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("StartVoiceChunkCapture() in VoiceCaptureComp.cpp")));
//CONVAI_LOG(LogTemp, Warning, TEXT("StartVoiceChunkCapture() in VoiceCaptureComp.cpp"));
UAudioMixerBlueprintLibrary::StartRecordingOutput(this, ExpectedRecordingTime, Cast<USoundSubmix>(AudioCaptureComponent->SoundSubmix));
}
void UConvaiPlayerComponent::ReadRecordedBuffer(Audio::AlignedFloatBuffer& RecordedBuffer, float& OutNumChannels, float& OutSampleRate)
{
if (Audio::FMixerDevice* MixerDevice = GetAudioMixerDeviceFromWorldContext(this))
{
// call the thing here.
RecordedBuffer = MixerDevice->StopRecording(Cast<USoundSubmix>(AudioCaptureComponent->SoundSubmix), OutNumChannels, OutSampleRate);
if (RecordedBuffer.Num() == 0)
{
//CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("ReadRecordedBuffer: No audio data. Did you call Start Recording Output?"));
}
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("ReadRecordedBuffer: Could not get MixerDevice"));
}
}
void UConvaiPlayerComponent::StartAudioCaptureComponent()
{
if (IsPixelStreamingEnabledAndAllowed())
{
PixelStreamingAudioComponent->Start();
}
else
{
AudioCaptureComponent->Start();
}
}
void UConvaiPlayerComponent::StopAudioCaptureComponent()
{
if (IsPixelStreamingEnabledAndAllowed())
{
PixelStreamingAudioComponent->Stop();
}
AudioCaptureComponent->Stop();
}
void UConvaiPlayerComponent::StopVoiceChunkCapture()
{
//USoundWave* SoundWaveMic = UAudioMixerBlueprintLibrary::StopRecordingOutput(this, EAudioRecordingExportType::SoundWave, "Convsound", "ConvSound", Cast<USoundSubmix>(AudioCaptureComponent->SoundSubmix));
float NumChannels;
float SampleRate;
Audio::AlignedFloatBuffer RecordedBuffer = Audio::AlignedFloatBuffer();
ReadRecordedBuffer(RecordedBuffer, NumChannels, SampleRate);
//NumChannels = 2;
//SampleRate = ConvaiConstants::VoiceCaptureSampleRate;
if (RecordedBuffer.Num() == 0)
return;
Audio::TSampleBuffer<int16> Int16Buffer = Audio::TSampleBuffer<int16>(RecordedBuffer, NumChannels, SampleRate);
TArray<int16> OutConverted;
if (NumChannels > 1 || SampleRate != ConvaiConstants::VoiceCaptureSampleRate)
{
UConvaiUtils::ResampleAudio(SampleRate, ConvaiConstants::VoiceCaptureSampleRate, NumChannels, true, (TArray<int16>)Int16Buffer.GetArrayView(), Int16Buffer.GetNumSamples(), OutConverted);
}
else
{
OutConverted = (TArray<int16>)Int16Buffer.GetArrayView();
}
//OutConverted = (TArray<int16>)Int16Buffer.GetArrayView();
//OutConverted = TArray<int16>(Int16Buffer.GetData(), Int16Buffer.GetNumSamples());
//CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("Int16Buffer.GetNumSamples() %i, NumChannels %f, SampleRate %f"), Int16Buffer.GetNumSamples(), NumChannels, SampleRate);
//CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("OutConverted.Num() %i"), OutConverted.Num());
if (IsRecording)
{
VoiceCaptureBuffer.Append((uint8*)OutConverted.GetData(), OutConverted.Num() * sizeof(int16));
return;
}
if (!ReplicateVoiceToNetwork)
{
if (IsStreaming)
VoiceCaptureRingBuffer.Enqueue((uint8*)OutConverted.GetData(), OutConverted.Num() * sizeof(int16));
onDataReceived_Delegate.ExecuteIfBound();
}
else
{
// Stream voice data
AddPCMDataToSend(TArray<uint8>((uint8*)OutConverted.GetData(), OutConverted.Num() * sizeof(int16)), false, ConvaiConstants::VoiceCaptureSampleRate, 1);
}
}
void UConvaiPlayerComponent::StartRecording()
{
if (IsRecording)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartRecording: already recording!"));
return;
}
if (IsStreaming)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartRecording: already talking!"));
return;
}
if (!IsInit)
{
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("StartRecording: Initializing..."));
if (!Init())
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartRecording: Could not initialize"));
return;
}
}
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("Started Recording "));
StartAudioCaptureComponent(); //Start the AudioCaptureComponent
// reset audio buffers
StartVoiceChunkCapture();
StopVoiceChunkCapture();
VoiceCaptureBuffer.Empty(ConvaiConstants::VoiceCaptureBufferSize);
IsRecording = true;
}
USoundWave* UConvaiPlayerComponent::FinishRecording()
{
if (!IsRecording)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("FinishRecording: did not start recording"));
return nullptr;
}
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("Stopped Recording "));
StopVoiceChunkCapture();
USoundWave* OutSoundWave = UConvaiUtils::PCMDataToSoundWav(VoiceCaptureBuffer, 1, ConvaiConstants::VoiceCaptureSampleRate);
StopAudioCaptureComponent(); //stop the AudioCaptureComponent
if (IsValid(OutSoundWave))
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("OutSoundWave->GetDuration(): %f seconds "), OutSoundWave->GetDuration());
IsRecording = false;
return OutSoundWave;
}
void UConvaiPlayerComponent::StartTalking(
UConvaiChatbotComponent* ConvaiChatbotComponent,
UConvaiEnvironment* Environment,
bool GenerateActions,
bool VoiceResponse,
bool RunOnServer,
bool StreamPlayerMic,
bool UseServerAPI_Key)
{
if (IsStreaming)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartTalking: already talking!"));
return;
}
if (IsRecording)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartTalking: already recording!"));
return;
}
if (!IsInit)
{
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("StartTalking Initializing..."));
if (!Init())
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartTalking Could not initialize"));
return;
}
}
if (!IsValid(ConvaiChatbotComponent))
{
if (RunOnServer)
{
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("StartTalking: ConvaiChatbotComponent is not valid, will still stream voice chat to network"));
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("StartTalking: ConvaiChatbotComponent is not valid"));
return;
}
}
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("Started Talking"));
StartAudioCaptureComponent(); //Start the AudioCaptureComponent
// reset audio buffers
StartVoiceChunkCapture();
StopVoiceChunkCapture();
IsStreaming = true;
VoiceCaptureRingBuffer.Empty();
ReplicateVoiceToNetwork = RunOnServer;
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
FString ClientAuthKey = UseServerAPI_Key ? FString("") : AuthKey;
// Ensure that the local version of the chatbot component has the ReplicateVoiceToNetwork set properly
ConvaiChatbotComponent->ReplicateVoiceToNetwork = RunOnServer;
if (RunOnServer)
{
if (IsValid(Environment))
StartTalkingServer(ConvaiChatbotComponent, true, Environment->Actions, Environment->Objects, Environment->Characters, Environment->MainCharacter, GenerateActions, VoiceResponse, StreamPlayerMic, UseServerAPI_Key, ClientAuthKey, AuthHeader);
else
StartTalkingServer(ConvaiChatbotComponent, false, TArray<FString>(), TArray<FConvaiObjectEntry>(), TArray<FConvaiObjectEntry>(), FConvaiObjectEntry(), GenerateActions, VoiceResponse, StreamPlayerMic, UseServerAPI_Key, ClientAuthKey, AuthHeader);
}
else
{
bool UseOverrideAuthKey = false;
ConvaiChatbotComponent->StartGetResponseStream(this, FString(""), Environment, GenerateActions, VoiceResponse, false, UseOverrideAuthKey, FString(""), FString(""), Token, SpeakerID);
CurrentConvaiChatbotComponent = ConvaiChatbotComponent;
}
}
void UConvaiPlayerComponent::FinishTalking()
{
if (!IsStreaming)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("FinishTalking did not start talking"));
return;
}
StopVoiceChunkCapture();
StopAudioCaptureComponent(); //stop the AudioCaptureComponent
IsStreaming = false;
if (ReplicateVoiceToNetwork)
{
FinishTalkingServer();
}
else
{
// Invalidate the token by generating a new one
GenerateNewToken();
if (IsValid(CurrentConvaiChatbotComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("FinishTalking calling FinishGetResponseStream"));
CurrentConvaiChatbotComponent->FinishGetResponseStream(this);
CurrentConvaiChatbotComponent = nullptr;
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("FinishTalking failed to call FinishGetResponseStream"));
}
}
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("Finished Talking"));
}
void UConvaiPlayerComponent::StartTalkingServer_Implementation(
class UConvaiChatbotComponent* ConvaiChatbotComponent,
bool EnvironemntSent,
const TArray<FString>& Actions,
const TArray<FConvaiObjectEntry>& Objects,
const TArray<FConvaiObjectEntry>& Characters,
FConvaiObjectEntry MainCharacter,
bool GenerateActions,
bool VoiceResponse,
bool StreamPlayerMic,
bool UseServerAPI_Key,
const FString& ClientAuthKey,
const FString& AuthHeader)
{
// if "StreamPlayerMic" is true then "bShouldMuteGlobal" should be false, meaning we will play the player's audio on other clients
bShouldMuteGlobal = !StreamPlayerMic;
// Make sure the ring buffer is empty
VoiceCaptureRingBuffer.Empty();
// if "ConvaiChatbotComponent" is valid then run StartGetResponseStream function
if (IsValid(ConvaiChatbotComponent))
{
UConvaiEnvironment* Environment = nullptr;
if (EnvironemntSent)
{
if (IsValid(ConvaiChatbotComponent->Environment))
Environment = ConvaiChatbotComponent->Environment;
else
{
Environment = UConvaiEnvironment::CreateConvaiEnvironment();
}
Environment->Actions = Actions;
Environment->Characters = Characters;
Environment->Objects = Objects;
Environment->MainCharacter = MainCharacter;
}
bool UseOverrideAuthKey = !UseServerAPI_Key;
ConvaiChatbotComponent->StartGetResponseStream(this, FString(""), Environment, GenerateActions, VoiceResponse, true, UseOverrideAuthKey, ClientAuthKey, AuthHeader, Token, SpeakerID);
CurrentConvaiChatbotComponent = ConvaiChatbotComponent;
}
}
void UConvaiPlayerComponent::FinishTalkingServer_Implementation()
{
// Invalidate the token by generating a new one
GenerateNewToken();
if (IsValid(CurrentConvaiChatbotComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("FinishTalking calling FinishGetResponseStream"));
CurrentConvaiChatbotComponent->FinishGetResponseStream(this);
CurrentConvaiChatbotComponent = nullptr;
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("FinishTalking failed to call FinishGetResponseStream"));
}
}
void UConvaiPlayerComponent::SendText(UConvaiChatbotComponent* ConvaiChatbotComponent, FString Text, UConvaiEnvironment* Environment, bool GenerateActions, bool VoiceResponse, bool RunOnServer, bool UseServerAPI_Key)
{
//FString Error;
//if (GenerateActions && !UConvaiActions::ValidateEnvironment(Environment, Error))
//{
// CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SendText: %s"), *Error);
// CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("SendText: Environment object seems to have issues -> setting GenerateActions to false"));
// GenerateActions = false;
//}
if (!IsValid(ConvaiChatbotComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SendText: ConvaiChatbotComponent is not valid"));
return;
}
if (Text.Len() == 0)
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SendText: Text is empty"));
return;
}
ReplicateVoiceToNetwork = RunOnServer;
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
FString ClientAuthKey = UseServerAPI_Key ? FString("") : AuthKey;
// Ensure that the local version of the chatbot component has the ReplicateVoiceToNetwork set properly
ConvaiChatbotComponent->ReplicateVoiceToNetwork = RunOnServer;
if (RunOnServer)
{
if (IsValid(Environment))
SendTextServer(ConvaiChatbotComponent, Text, true, Environment->Actions, Environment->Objects, Environment->Characters, Environment->MainCharacter, GenerateActions, VoiceResponse, UseServerAPI_Key, ClientAuthKey, AuthHeader);
else
SendTextServer(ConvaiChatbotComponent, Text, false, TArray<FString>(), TArray<FConvaiObjectEntry>(), TArray<FConvaiObjectEntry>(), FConvaiObjectEntry(), GenerateActions, VoiceResponse, UseServerAPI_Key, ClientAuthKey, AuthHeader);
}
else
{
bool UseOverrideAuthKey = false;
ConvaiChatbotComponent->StartGetResponseStream(this, Text, Environment, GenerateActions, VoiceResponse, false, UseOverrideAuthKey, FString(""), FString(""), Token, SpeakerID);
// Invalidate the token by generating a new one
GenerateNewToken();
CurrentConvaiChatbotComponent = ConvaiChatbotComponent;
}
}
void UConvaiPlayerComponent::SendTextServer_Implementation(
UConvaiChatbotComponent* ConvaiChatbotComponent,
const FString& Text,
bool EnvironemntSent,
const TArray<FString>& Actions,
const TArray<FConvaiObjectEntry>& Objects,
const TArray<FConvaiObjectEntry>& Characters,
FConvaiObjectEntry MainCharacter,
bool GenerateActions,
bool VoiceResponse,
bool UseServerAPI_Key,
const FString& ClientAuthKey,
const FString& AuthHeader)
{
if (!IsValid(ConvaiChatbotComponent))
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("SendTextServer: ConvaiChatbotComponent is not valid"));
return;
}
UConvaiEnvironment* Environment;
if (IsValid(ConvaiChatbotComponent->Environment))
Environment = ConvaiChatbotComponent->Environment;
else
{
Environment = UConvaiEnvironment::CreateConvaiEnvironment();
}
Environment->Actions = Actions;
Environment->Characters = Characters;
Environment->Objects = Objects;
Environment->MainCharacter = MainCharacter;
bool UseOverrideAuthKey = !UseServerAPI_Key;
ConvaiChatbotComponent->StartGetResponseStream(this, Text, Environment, GenerateActions, VoiceResponse, true, UseOverrideAuthKey, ClientAuthKey, AuthHeader, Token, SpeakerID);
// Invalidate the token by generating a new one
GenerateNewToken();
CurrentConvaiChatbotComponent = ConvaiChatbotComponent;
}
bool UConvaiPlayerComponent::ShouldMuteLocal()
{
return true;
}
bool UConvaiPlayerComponent::ShouldMuteGlobal()
{
return bShouldMuteGlobal;
}
void UConvaiPlayerComponent::OnServerAudioReceived(uint8* VoiceData, uint32 VoiceDataSize, bool ContainsHeaderData, uint32 SampleRate, uint32 NumChannels)
{
//if (IsRecording)
// VoiceCaptureBuffer.Append(VoiceData, VoiceDataSize);
//if (IsStreaming)
// VoiceCaptureRingBuffer.Enqueue(VoiceData, VoiceDataSize);
//CONVAI_LOG(ConvaiPlayerLog, Log, TEXT("OnServerAudioReceived received %d bytes"), VoiceDataSize);
//onDataReceived_Delegate.ExecuteIfBound();
// Simply enque the data into the ring buffer
VoiceCaptureRingBuffer.Enqueue(VoiceData, VoiceDataSize);
}
void UConvaiPlayerComponent::BeginPlay()
{
Super::BeginPlay();
if (IsValid(AudioCaptureComponent))
{
AudioCaptureComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Error, TEXT("Could not attach AudioCaptureComponent"));
}
if (!IsInit)
{
if (!Init())
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("Could not initialize Audio Decoder"));
return;
}
}
// Register with the ConvaiSubsystem
if (UGameInstance* GameInstance = GetWorld()->GetGameInstance())
{
if (UConvaiSubsystem* ConvaiSubsystem = GameInstance->GetSubsystem<UConvaiSubsystem>())
{
if (IsValid(ConvaiSubsystem))
{
ConvaiSubsystem->RegisterPlayerComponent(this);
}
else
{
CONVAI_LOG(ConvaiPlayerLog, Warning, TEXT("BeginPlay: ConvaiSubsystem is not valid"));
}
}
}
}
bool UConvaiPlayerComponent::ConsumeStreamingBuffer(TArray<uint8>& Buffer)
{
int Datalength = VoiceCaptureRingBuffer.RingDataUsage();
if (Datalength <= 0)
return false;
Buffer.SetNumUninitialized(Datalength, false);
VoiceCaptureRingBuffer.Dequeue(Buffer.GetData(), Datalength);
return true;
}
void UConvaiPlayerComponent::SetIsStreamingServer_Implementation(bool value)
{
IsStreaming = value;
if (IsStreaming == false)
onDataReceived_Delegate.ExecuteIfBound(); // In case a consumer was waiting on this delegate
}
void UConvaiPlayerComponent::BeginDestroy()
{
// Fallback unregistration in case EndPlay wasn't called
if (UGameInstance* GameInstance = GetWorld() ? GetWorld()->GetGameInstance() : nullptr)
{
if (UConvaiSubsystem* ConvaiSubsystem = GameInstance->GetSubsystem<UConvaiSubsystem>())
{
if (IsValid(ConvaiSubsystem))
{
ConvaiSubsystem->UnregisterPlayerComponent(this);
}
}
}
Super::BeginDestroy();
}

View File

@@ -0,0 +1,232 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiSpeechToTextProxy.h"
#include "ConvaiUtils.h"
#include "Sound/SoundWave.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "AudioDecompress.h"
#include "Engine.h"
#include "JsonObjectConverter.h"
#include "../Convai.h"
namespace
{
static FString TextToSpeechURL() { return UConvaiURL::GetFullURL(TEXT("tts"), false); }
static FString SpeechToTextURL() { return UConvaiURL::GetFullURL(TEXT("stt/"), false); }
}
DEFINE_LOG_CATEGORY(ConvaiS2THttpLog);
UConvaiSpeechToTextProxy* UConvaiSpeechToTextProxy::CreateSpeech2TextFromFileNameQueryProxy(UObject* WorldContextObject, FString filename)
{
UConvaiSpeechToTextProxy* Proxy = NewObject<UConvaiSpeechToTextProxy>();
Proxy->WorldPtr = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
Proxy->URL = SpeechToTextURL();
if (!FPaths::FileExists(filename))
{
// check if the file is relative to the save/BouncedWavFiles directory
FString SaveDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir());
filename = FPaths::Combine(SaveDir, FString("BouncedWavFiles"), filename);
if (!FPaths::FileExists(filename))
{
//if (!FPaths::GameAgnosticSavedDir)
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("File does not exist!, %s"), *filename);
Proxy->failed();
return nullptr;
}
}
// Read the file into a byte array
FFileHelper::LoadFileToArray(Proxy->Payload, *filename, 0);
Proxy->bStereo = true;
return Proxy;
}
UConvaiSpeechToTextProxy* UConvaiSpeechToTextProxy::CreateSpeech2TextFromSoundWaveQueryProxy(UObject* WorldContextObject, USoundWave* SoundWave)
{
UConvaiSpeechToTextProxy* Proxy = NewObject<UConvaiSpeechToTextProxy>();
Proxy->WorldPtr = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
Proxy->URL = SpeechToTextURL();
if (SoundWave == nullptr)
{
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("Sound wave is invalid!"));
Proxy->failed();
return nullptr;
}
//Proxy->Payload.SetNum(SoundWave->TotalSamples * 2);
uint8* PCMData = nullptr;
TArray<uint8> RawPCMData;
int32 OutSampleRate = -1;
int32 outNumChannels = -1;
//int32 numBytes = SoundWave->GeneratePCMData(PCMData, SoundWave->TotalSamples);
if (SoundWave->RawPCMData == nullptr || SoundWave->RawPCMDataSize <= 0) {
CONVAI_LOG(LogTemp, Display, TEXT("SoundWave PCM Data is compressed. Starting Decompressing....."));
RawPCMData = UConvaiUtils::ExtractPCMDataFromSoundWave(SoundWave, OutSampleRate, outNumChannels);
if (RawPCMData.Num() > 0) {
CONVAI_LOG(LogTemp, Display, TEXT("SoundWave PCM Data decompression successfully done....."));
}
else {
CONVAI_LOG(LogTemp,Warning,TEXT("SoundWave couldn't be decompressed successuflly !!!"));
}
//CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("RawPCMData is invalid!"));
}
else {
RawPCMData = TArray<uint8>(SoundWave->RawPCMData, SoundWave->RawPCMDataSize);
}
//TArray<uint8> AudioBuffer(SoundWave->RawPCMData, SoundWave->RawPCMDataSize);
SerializeWaveFile(Proxy->Payload, RawPCMData.GetData(), RawPCMData.Num(), SoundWave->NumChannels, SoundWave->GetSampleRateForCurrentPlatform());
//CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("Sound wave sample rate: %f"), SoundWave->GetSampleRateForCurrentPlatform());
Proxy->bStereo = SoundWave->NumChannels>1? true : false;
return Proxy;
}
UConvaiSpeechToTextProxy* UConvaiSpeechToTextProxy::CreateSpeech2TextFromArrayQueryProxy(UObject* WorldContextObject, TArray<uint8> Payload)
{
UConvaiSpeechToTextProxy* Proxy = NewObject<UConvaiSpeechToTextProxy>();
Proxy->WorldPtr = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
Proxy->URL = SpeechToTextURL();
Proxy->Payload = Payload;
Proxy->bStereo = true;
return Proxy;
}
void UConvaiSpeechToTextProxy::Activate()
{
UWorld* World = WorldPtr.Get();
if (!World)
{
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("Could not get a pointer to world!"));
failed();
return;
}
FHttpModule* Http = &FHttpModule::Get();
if (!Http)
{
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("Could not get a pointer to http module!"));
failed();
return;
}
if (Payload.Num() <= 44)
{
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("Payload size is too small, %d bytes!"), Payload.Num());
failed();
return;
}
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
// Form Validation
if (!UConvaiFormValidation::ValidateAuthKey(AuthKey) || !UConvaiFormValidation::ValidateInputVoice(Payload))
{
failed();
return;
}
TArray<uint8> monoWavBytes;
if (bStereo)
{
// Change the wav file from 2 channels to 1 channel
UConvaiUtils::StereoToMono(Payload, monoWavBytes);
}
else
{
monoWavBytes = Payload;
}
// Create the request
FHttpRequestRef Request = Http->CreateRequest();
Request->OnProcessRequestComplete().BindUObject(this, &UConvaiSpeechToTextProxy::onHttpRequestComplete);
// Set request fields
Request->SetURL(URL);
Request->SetVerb("POST");
Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
Request->SetHeader("Content-Type", "multipart/form-data; boundary=blahblahsomeboundary");
Request->SetHeader(AuthHeader, AuthKey);
// prepare request content data
//FString a = "\r\n--blahblahsomeboundary\r\n";
//FString b = "Content-Disposition: form-data; name=\"Request->SetHeader(AuthHeader, AuthKey);\"\r\n\r\n";
// Request->SetHeader(AuthHeader, AuthKey);
FString c = "\r\n--blahblahsomeboundary\r\n";
FString d = "Content-Disposition: form-data; name=\"file\"; filename=\"out.wav\"\r\n\r\n";
// UpFileRawData
FString e = "\r\n--blahblahsomeboundary--\r\n";
TArray<uint8> data;
//data.Append((uint8*)TCHAR_TO_UTF8(*a), a.Len());
//data.Append((uint8*)TCHAR_TO_UTF8(*b), b.Len());
//data.Append((uint8*)TCHAR_TO_UTF8(*Request->SetHeader(AuthHeader, AuthKey);), Request->SetHeader(AuthHeader, AuthKey);.Len());
data.Append((uint8*)TCHAR_TO_UTF8(*c), c.Len());
data.Append((uint8*)TCHAR_TO_UTF8(*d), d.Len());
data.Append(monoWavBytes);
data.Append((uint8*)TCHAR_TO_UTF8(*e), e.Len());
Request->SetContent(data);
// Run the request
if (!Request->ProcessRequest()) failed();
}
void UConvaiSpeechToTextProxy::onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful)
{
if (!bWasSuccessful || ResponsePtr->GetResponseCode() < 200 || ResponsePtr->GetResponseCode() > 299)
{
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("HTTP request failed with code %d"), ResponsePtr->GetResponseCode());
CONVAI_LOG(ConvaiS2THttpLog, Warning, TEXT("Response:%s"), *ResponsePtr->GetContentAsString());
this->Response = ResponsePtr->GetContentAsString();
failed();
return;
}
this->Response = ResponsePtr->GetContentAsString();
// Clean the string
this->Response = this->Response.LeftChop(3);
this->Response = this->Response.RightChop(12);
success();
}
void UConvaiSpeechToTextProxy::failed()
{
OnFailure.Broadcast(Response);
finish();
}
void UConvaiSpeechToTextProxy::success()
{
OnSuccess.Broadcast(Response);
finish();
}
void UConvaiSpeechToTextProxy::finish()
{
}

View File

@@ -0,0 +1,481 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiSubsystem.h"
#include "ConvaiAndroid.h"
#include "Engine/Engine.h"
#include "Async/Async.h"
#include "../Convai.h"
#include "ConvaiUtils.h"
#include "HAL/PlatformProcess.h"
#include "ConvaiChatbotComponent.h"
#include "ConvaiPlayerComponent.h"
THIRD_PARTY_INCLUDES_START
// grpc includes
#include <grpc++/grpc++.h>
#include <chrono>
THIRD_PARTY_INCLUDES_END
using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Status;
using service::ConvaiService;
DEFINE_LOG_CATEGORY(ConvaiSubsystemLog);
using grpc::SslCredentialsOptions;
#if PLATFORM_WINDOWS
#include <wincrypt.h>
namespace
{
std::string utf8Encode(const std::wstring& wstr)
{
if (wstr.empty())
return std::string();
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(),
NULL, 0, NULL, NULL);
std::string strTo(sizeNeeded, 0);
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(),
&strTo[0], sizeNeeded, NULL, NULL);
return strTo;
}
SslCredentialsOptions getSslOptions() {
SslCredentialsOptions result;
std::string pem_root_certs = R"(-----BEGIN CERTIFICATE-----
MIIFjDCCA3SgAwIBAgINAgCOsgIzNmWLZM3bmzANBgkqhkiG9w0BAQsFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw
MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFENDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKvAqqPCE27l0w9zC8dTPIE89bA+xTmDaG7y7VfQ4c+mOWhl
UebUQpK0yv2r678RJExK0HWDjeq+nLIHN1Em5j6rARZixmyRSjhIR0KOQPGBMUld
saztIIJ7O0g/82qj/vGDl//3t4tTqxiRhLQnTLXJdeB+2DhkdU6IIgx6wN7E5NcU
H3Rcsejcqj8p5Sj19vBm6i1FhqLGymhMFroWVUGO3xtIH91dsgy4eFKcfKVLWK3o
2190Q0Lm/SiKmLbRJ5Au4y1euFJm2JM9eB84Fkqa3ivrXWUeVtye0CQdKvsY2Fka
zvxtxvusLJzLWYHk55zcRAacDA2SeEtBbQfD1qsCAwEAAaOCAXYwggFyMA4GA1Ud
DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T
AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUJeIYDrJXkZQq5dRdhpCD3lOzuJIwHwYD
VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG
CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw
AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt
MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsME0G
A1UdIARGMEQwCAYGZ4EMAQIBMDgGCisGAQQB1nkCBQMwKjAoBggrBgEFBQcCARYc
aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAgEA
IVToy24jwXUr0rAPc924vuSVbKQuYw3nLflLfLh5AYWEeVl/Du18QAWUMdcJ6o/q
FZbhXkBH0PNcw97thaf2BeoDYY9Ck/b+UGluhx06zd4EBf7H9P84nnrwpR+4GBDZ
K+Xh3I0tqJy2rgOqNDflr5IMQ8ZTWA3yltakzSBKZ6XpF0PpqyCRvp/NCGv2KX2T
uPCJvscp1/m2pVTtyBjYPRQ+QuCQGAJKjtN7R5DFrfTqMWvYgVlpCJBkwlu7+7KY
3cTIfzE7cmALskMKNLuDz+RzCcsYTsVaU7Vp3xL60OYhqFkuAOOxDZ6pHOj9+OJm
YgPmOT4X3+7L51fXJyRH9KfLRP6nT31D5nmsGAOgZ26/8T9hsBW1uo9ju5fZLZXV
VS5H0HyIBMEKyGMIPhFWrlt/hFS28N1zaKI0ZBGD3gYgDLbiDT9fGXstpk+Fmc4o
lVlWPzXe81vdoEnFbr5M272HdgJWo+WhT9BYM0Ji+wdVmnRffXgloEoluTNcWzc4
1dFpgJu8fF3LG0gl2ibSYiCi9a6hvU0TppjJyIWXhkJTcMJlPrWx1VytEUGrX2l0
JDwRjW/656r0KVB02xHRKvm2ZKI03TglLIpmVCK3kBKkKNpBNkFt8rhafcCKOb9J
x/9tpNFlQTl7B39rJlJWkR17QnZqVptFePFORoZmFzM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)";
result.pem_root_certs = pem_root_certs;
return result;
}
SslCredentialsOptions getSslOptionsystem()
{
// Start with hardcoded certificates
SslCredentialsOptions result = getSslOptions();
// Open root certificate store.
HANDLE hRootCertStore = CertOpenSystemStoreW(NULL, L"ROOT");
if (!hRootCertStore)
return result;
// Get all root certificates and append them.
PCCERT_CONTEXT pCert = NULL;
while ((pCert = CertEnumCertificatesInStore(hRootCertStore, pCert)) != NULL)
{
// Append this certificate in PEM formatted data.
DWORD size = 0;
CryptBinaryToStringW(pCert->pbCertEncoded, pCert->cbCertEncoded,
CRYPT_STRING_BASE64HEADER, NULL, &size);
std::vector<WCHAR> pem(size);
CryptBinaryToStringW(pCert->pbCertEncoded, pCert->cbCertEncoded,
CRYPT_STRING_BASE64HEADER, pem.data(), &size);
result.pem_root_certs += utf8Encode(pem.data());
}
CertCloseStore(hRootCertStore, 0);
return result;
}
};
#endif
namespace
{
const char* grpc_connectivity_state_str[] =
{
"GRPC_CHANNEL_IDLE",
"GRPC_CHANNEL_CONNECTING",
"GRPC_CHANNEL_READY",
"GRPC_CHANNEL_TRANSIENT_FAILURE",
"GRPC_CHANNEL_SHUTDOWN"
};
};
uint32 FgRPCClient::Run()
{
void* got_tag;
bool ok = false;
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Start Run"));
// Block until the next result is available in the completion queue "cq
while (bIsRunning && cq_.Next(&got_tag, &ok)) {
if (got_tag)
{
FgRPC_Delegate* gRPC_Delegate = static_cast<FgRPC_Delegate*>(got_tag);
if (bIsRunning)
{
gRPC_Delegate->ExecuteIfBound(ok);
}
else
{
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Could not run gRPC delegate due to thread closing down"));
}
}
else
{
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Bad got_tag"));
}
}
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("End Run"));
return 0;
}
void FgRPCClient::StartStub()
{
OnStateChangeDelegate = FgRPC_Delegate::CreateRaw(this, &FgRPCClient::OnStateChange);
CreateChannel();
bIsRunning = true;
Thread.Reset(FRunnableThread::Create(this, TEXT("gRPC_Stub")));
}
void FgRPCClient::CreateChannel()
{
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("gRPC Creating Channel..."));
grpc::ChannelArguments args;
args.SetMaxReceiveMessageSize(2147483647);
args.SetInt(GRPC_ARG_CLIENT_IDLE_TIMEOUT_MS, INT_MAX);
args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30000); // Send keepalive ping every 30 seconds
args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 60000); // Wait 5 seconds for keepalive response
args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1); // Allow keepalive even when no active calls
Channel = grpc::CreateCustomChannel(Target, Creds, args);
//Channel->NotifyOnStateChange(grpc_connectivity_state::GRPC_CHANNEL_CONNECTING, std::chrono::system_clock::time_point().max(), &cq_, (void*)&OnStateChangeDelegate);
}
void FgRPCClient::OnStateChange(bool ok)
{
grpc_connectivity_state state;
if (Channel)
state = Channel->GetState(true);
else
state = grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN;
if (state == grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN)
{
if (!bIsRunning)
{
CONVAI_LOG(ConvaiSubsystemLog, Warning, TEXT("gRPC channel state changed to %s... Attempting to reconnect"), *FString(grpc_connectivity_state_str[state]));
CreateChannel();
}
else
{
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("gRPC channel state changed to %s... Closing"), *FString(grpc_connectivity_state_str[state]));
}
//Channel->NotifyOnStateChange(state, std::chrono::system_clock::time_point().max(), &cq_, (void*)&OnStateChangeDelegate);
}
else
{
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("gRPC channel state changed to %s"), *FString(grpc_connectivity_state_str[state]));
Channel->NotifyOnStateChange(state, std::chrono::system_clock::time_point().max(), &cq_, (void*)&OnStateChangeDelegate);
}
}
void FgRPCClient::Exit()
{
if (!bIsRunning)
{
return;
}
bIsRunning = false;
{
FScopeLock Lock(&CriticalSection);
cq_.Shutdown();
}
}
FgRPCClient::FgRPCClient(std::string InTarget,
const std::shared_ptr<grpc::ChannelCredentials>& InCreds)
: bIsRunning(false),
Creds(InCreds),
Target(InTarget)
{
}
std::unique_ptr<ConvaiService::Stub> FgRPCClient::GetNewStub()
{
FScopeLock Lock(&CriticalSection);
// # TODO (Mohamed): Handling Mic permissions requires refactoring and a unified pipeline for all platforms
// Delaying asking for permission for MacOS due to a crash
#ifdef __APPLE__
GetAppleMicPermission();
#endif
grpc_connectivity_state state = Channel->GetState(false);
if (state != grpc_connectivity_state::GRPC_CHANNEL_READY)
{
CONVAI_LOG(ConvaiSubsystemLog, Warning, TEXT("gRPC channel not ready yet.. Current State: %s"), *FString(grpc_connectivity_state_str[state]));
}
return ConvaiService::NewStub(Channel);
}
CompletionQueue* FgRPCClient::GetCompletionQueue()
{
return &cq_;
}
UConvaiSubsystem::UConvaiSubsystem()
: UGameInstanceSubsystem()
{
}
void UConvaiSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
//AsyncTask(ENamedThreads::GameThread, [WeakThis = MakeWeakObjectPtr(this)]
// {
// Check command line for insecure connection flag first, then fall back to settings
bool AllowInsecureConnection = false;
FString InsecureConnectionStr = UCommandLineUtils::GetCommandLineFlagValueAsString(TEXT("ConvaiAllowInsecure"), TEXT(""));
if (!InsecureConnectionStr.IsEmpty())
{
// Convert string to bool
AllowInsecureConnection = InsecureConnectionStr.ToBool();
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Using insecure connection setting from command line: %s"),
AllowInsecureConnection ? TEXT("true") : TEXT("false"));
}
else
{
// Use setting from ConvaiSettings
AllowInsecureConnection = Convai::Get().GetConvaiSettings()->AllowInsecureConnection;
}
std::shared_ptr<grpc::ChannelCredentials> channel_creds;
#if PLATFORM_WINDOWS
if (AllowInsecureConnection)
channel_creds = grpc::InsecureChannelCredentials();
else
{
// Check if we should use system certificates (can be overridden by command line)
bool UseSystemCerts = Convai::Get().GetConvaiSettings()->UseSystemCertificates;
FString UseSystemCertsStr = UCommandLineUtils::GetCommandLineFlagValueAsString(TEXT("ConvaiUseSystemCerts"), TEXT(""));
if (!UseSystemCertsStr.IsEmpty())
{
UseSystemCerts = UseSystemCertsStr.ToBool();
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Using system certificates setting from command line: %s"),
UseSystemCerts ? TEXT("true") : TEXT("false"));
}
if (UseSystemCerts)
{
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Using both hardcoded and system SSL certificates"));
channel_creds = grpc::SslCredentials(getSslOptionsystem());
}
else
{
channel_creds = grpc::SslCredentials(getSslOptions());
}
}
#else
if (AllowInsecureConnection)
channel_creds = grpc::InsecureChannelCredentials();
else
channel_creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
#endif
// Get URL from command line first, then settings, then default
FString URL = Convai::Get().GetConvaiSettings()->CustomURL;
URL.TrimEndInline();
URL.TrimStartInline();
// Check for command line parameter
FString CommandLineURL = UCommandLineUtils::GetCommandLineFlagValueAsString(TEXT("ConvaiStreamURL"), TEXT(""));
if (!CommandLineURL.IsEmpty())
{
URL = CommandLineURL;
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("Using stream URL from command line: %s"), *URL);
}
// If settings URL is empty, use default
else if (URL.IsEmpty())
{
URL = "stream.convai.com";
}
gRPC_Runnable = MakeShareable(new FgRPCClient(TCHAR_TO_UTF8(*URL), channel_creds));
//gRPC_Runnable = MakeShareable(new FgRPCClient(std::string("0.tcp.us-cal-1.ngrok.io:13976"), channel_creds));
gRPC_Runnable->StartStub();
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("UConvaiSubsystem Started"));
#if PLATFORM_ANDROID
GetAndroidMicPermission();
#endif
//});
}
void UConvaiSubsystem::Deinitialize()
{
gRPC_Runnable->Exit();
Super::Deinitialize();
CONVAI_LOG(ConvaiSubsystemLog, Log, TEXT("UConvaiSubsystem Stopped"));
}
void UConvaiSubsystem::GetAndroidMicPermission()
{
if (!UConvaiAndroid::ConvaiAndroidHasMicrophonePermission())
UConvaiAndroid::ConvaiAndroidAskMicrophonePermission();
}
void UConvaiSubsystem::RegisterChatbotComponent(UConvaiChatbotComponent* ChatbotComponent)
{
if (IsValid(ChatbotComponent) && !RegisteredChatbotComponents.Contains(ChatbotComponent))
{
RegisteredChatbotComponents.Add(ChatbotComponent);
}
}
void UConvaiSubsystem::UnregisterChatbotComponent(UConvaiChatbotComponent* ChatbotComponent)
{
if (RegisteredChatbotComponents.Contains(ChatbotComponent))
{
RegisteredChatbotComponents.Remove(ChatbotComponent);
}
}
TArray<UConvaiChatbotComponent*> UConvaiSubsystem::GetAllChatbotComponents() const
{
return RegisteredChatbotComponents;
}
void UConvaiSubsystem::RegisterPlayerComponent(UConvaiPlayerComponent* PlayerComponent)
{
if (IsValid(PlayerComponent) && !RegisteredPlayerComponents.Contains(PlayerComponent))
{
RegisteredPlayerComponents.Add(PlayerComponent);
CONVAI_LOG(ConvaiSubsystemLog, Verbose, TEXT("Registered player component: %s"), *PlayerComponent->GetName());
}
}
void UConvaiSubsystem::UnregisterPlayerComponent(UConvaiPlayerComponent* PlayerComponent)
{
if (RegisteredPlayerComponents.Contains(PlayerComponent))
{
RegisteredPlayerComponents.Remove(PlayerComponent);
CONVAI_LOG(ConvaiSubsystemLog, Verbose, TEXT("Unregistered player component: %s"), *PlayerComponent->GetName());
}
}
TArray<UConvaiPlayerComponent*> UConvaiSubsystem::GetAllPlayerComponents() const
{
return RegisteredPlayerComponents;
}

View File

@@ -0,0 +1,155 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#include "ConvaiTextToSpeechProxy.h"
#include "ConvaiUtils.h"
#include "Containers/UnrealString.h"
#include "Sound/SoundWave.h"
#include "Engine.h"
#include "JsonObjectConverter.h"
#include "RestAPI/ConvaiURL.h"
#include "../Convai.h"
DEFINE_LOG_CATEGORY(ConvaiT2SHttpLog);
namespace {
const char* TTS_Voice_Type_str[] = {
"MALE",
"FEMALE",
"WUKMale 1",
"WUKFemale 1",
"SUKMale 1",
"WAFemale 1",
"WAMale 1",
"SIFemale 1",
"SIMale 1",
"SUFemale 1",
"SUMale 1",
"WUFemale 1",
"WUMale 1",
"Trixie",
"Twilight Sparkle",
"Celestia",
"Spike",
"Applejack"
};
};
UConvaiTextToSpeechProxy* UConvaiTextToSpeechProxy::CreateTextToSpeechQueryProxy(UObject* WorldContextObject, FString Transcript, FString Voice)
{
UConvaiTextToSpeechProxy* Proxy = NewObject<UConvaiTextToSpeechProxy>();
Proxy->WorldPtr = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
Proxy->URL = UConvaiURL::GetFullURL(TEXT("tts"), false);
Proxy->Transcript = Transcript;
Proxy->VoiceStr = Voice;
//Proxy->VoiceStr = FString(TTS_Voice_Type_str[uint8(Voice)]);
return Proxy;
}
void UConvaiTextToSpeechProxy::Activate()
{
UWorld* World = WorldPtr.Get();
if (!World)
{
CONVAI_LOG(ConvaiT2SHttpLog, Warning, TEXT("Could not get a pointer to world!"));
failed();
return;
}
FHttpModule* Http = &FHttpModule::Get();
if (!Http)
{
CONVAI_LOG(ConvaiT2SHttpLog, Warning, TEXT("Could not get a pointer to http module!"));
failed();
return;
}
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
// Form Validation
if (!UConvaiFormValidation::ValidateAuthKey(AuthKey) || !UConvaiFormValidation::ValidateInputText(Transcript) || !UConvaiFormValidation::ValidateVoiceType(VoiceStr))
{
failed();
return;
}
// Create the request
FHttpRequestRef Request = Http->CreateRequest();
Request->OnProcessRequestComplete().BindUObject(this, &UConvaiTextToSpeechProxy::onHttpRequestComplete);
// Set request fields
Request->SetURL(URL);
Request->SetVerb("POST");
Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
Request->SetHeader(AuthHeader, AuthKey);
// prepare json data
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);
JsonWriter->WriteObjectStart();
JsonWriter->WriteValue(AuthHeader, AuthKey);
JsonWriter->WriteValue("transcript", Transcript);
JsonWriter->WriteValue("voice", VoiceStr);
JsonWriter->WriteValue("filename", FString("testAudio"));
//JsonWriter->WriteValue("encoding", (VoiceStr.ToLower() == "female" || VoiceStr.ToLower() == "male")? FString("wav") : FString("mp3"));
JsonWriter->WriteValue("encoding", FString("wav"));
JsonWriter->WriteObjectEnd();
JsonWriter->Close();
// Insert the content into the request
Request->SetContentAsString(JsonString);
// Debug
//CONVAI_LOG(ConvaiT2SHttpLog, Warning, TEXT("%s"), *UConvaiUtils::ByteArrayToString(Request->GetContent()));
// Initiate the request
if (!Request->ProcessRequest()) failed();
}
void UConvaiTextToSpeechProxy::onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful)
{
if (!bWasSuccessful || ResponsePtr->GetResponseCode() < 200 || ResponsePtr->GetResponseCode() > 299)
{
CONVAI_LOG(ConvaiT2SHttpLog, Warning, TEXT("HTTP request failed with code %d, and with response:%s"),ResponsePtr->GetResponseCode(), *ResponsePtr->GetContentAsString());
failed();
return;
}
this->SoundWave = UConvaiUtils::WavDataToSoundWave(ResponsePtr->GetContent());
//this->SoundWave = UConvaiUtils::PCMDataToSoundWav(ResponsePtr->GetContent(), 1, 44100); // use this if in PCM format
if (this->SoundWave == nullptr)
{
CONVAI_LOG(ConvaiT2SHttpLog, Warning, TEXT("Failed to decode response content to a sound wave"));
failed();
return;
}
success();
}
void UConvaiTextToSpeechProxy::failed()
{
OnFailure.Broadcast(SoundWave);
finish();
}
void UConvaiTextToSpeechProxy::success()
{
OnSuccess.Broadcast(SoundWave);
finish();
}
void UConvaiTextToSpeechProxy::finish()
{
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
#ifdef __APPLE__
#import <AVFoundation/AVFoundation.h>
bool GetAppleMicPermission()
{
__block bool permissionGranted = false;
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio])
{
case AVAuthorizationStatusAuthorized:
// Already authorized
permissionGranted = true;
break;
case AVAuthorizationStatusNotDetermined:
{
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
// The user has not yet been asked to grant access to the audio capture device.
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted)
{
if (granted)
{
// Permission granted
permissionGranted = true;
}
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
break;
}
case AVAuthorizationStatusRestricted:
case AVAuthorizationStatusDenied:
// The user has previously denied access or can't grant access.
permissionGranted = false;
break;
}
return permissionGranted;
}
#endif

View File

@@ -0,0 +1,72 @@
#if defined(_WIN32) || defined(__ANDROID__)
#if defined(_MSC_VER)
#pragma warning (disable:4018) // 'expression' : signed/unsigned mismatch
#pragma warning (disable:4065) // switch statement contains 'default' but no 'case' labels
#pragma warning (disable:4146) // unary minus operator applied to unsigned type, result still unsigned
#pragma warning (disable:4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data
#pragma warning (disable:4251) // 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
#pragma warning (disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
#pragma warning (disable:4305) // 'identifier' : truncation from 'type1' to 'type2'
#pragma warning (disable:4307) // 'operator' : integral constant overflow
#pragma warning (disable:4309) // 'conversion' : truncation of constant value
#pragma warning (disable:4334) // 'operator' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)
#pragma warning (disable:4355) // 'this' : used in base member initializer list
#pragma warning (disable:4506) // no definition for inline function 'function'
#pragma warning (disable:4996) // The compiler encountered a deprecated declaration.
#pragma warning (disable:4125) // decimal digit terminates octal escape sequence
#pragma warning (disable:4800) // decimal digit terminates octal escape sequence
#endif
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: arkit_blend_shapes.proto
#include "arkit_blend_shapes.pb.h"
#include "arkit_blend_shapes.grpc.pb.h"
#include <functional>
#include <grpcpp/impl/codegen/async_stream.h>
#include <grpcpp/impl/codegen/async_unary_call.h>
#include <grpcpp/impl/codegen/channel_interface.h>
#include <grpcpp/impl/codegen/client_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/message_allocator.h>
#include <grpcpp/impl/codegen/method_handler.h>
#include <grpcpp/impl/codegen/rpc_service_method.h>
#include <grpcpp/impl/codegen/server_callback.h>
#include <grpcpp/impl/codegen/server_callback_handlers.h>
#include <grpcpp/impl/codegen/server_context.h>
#include <grpcpp/impl/codegen/service_type.h>
#include <grpcpp/impl/codegen/sync_stream.h>
namespace service {
} // namespace service
#else
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: arkit_blend_shapes.proto
#include "arkit_blend_shapes.pb.h"
#include "arkit_blend_shapes.grpc.pb.h"
#include <functional>
#include <grpcpp/support/async_stream.h>
#include <grpcpp/support/async_unary_call.h>
#include <grpcpp/impl/codegen/channel_interface.h>
#include <grpcpp/impl/codegen/client_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/message_allocator.h>
#include <grpcpp/impl/codegen/method_handler.h>
#include <grpcpp/impl/codegen/rpc_service_method.h>
#include <grpcpp/impl/codegen/server_callback.h>
#include <grpcpp/impl/codegen/server_callback_handlers.h>
#include <grpcpp/impl/codegen/server_context.h>
#include <grpcpp/impl/codegen/service_type.h>
#include <grpcpp/impl/codegen/sync_stream.h>
namespace service {
} // namespace service
#endif

View File

@@ -0,0 +1,89 @@
#if defined(_WIN32) || defined(__ANDROID__)
#if defined(_MSC_VER)
#pragma warning (disable:4018) // 'expression' : signed/unsigned mismatch
#pragma warning (disable:4065) // switch statement contains 'default' but no 'case' labels
#pragma warning (disable:4146) // unary minus operator applied to unsigned type, result still unsigned
#pragma warning (disable:4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data
#pragma warning (disable:4251) // 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
#pragma warning (disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
#pragma warning (disable:4305) // 'identifier' : truncation from 'type1' to 'type2'
#pragma warning (disable:4307) // 'operator' : integral constant overflow
#pragma warning (disable:4309) // 'conversion' : truncation of constant value
#pragma warning (disable:4334) // 'operator' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)
#pragma warning (disable:4355) // 'this' : used in base member initializer list
#pragma warning (disable:4506) // no definition for inline function 'function'
#pragma warning (disable:4996) // The compiler encountered a deprecated declaration.
#pragma warning (disable:4125) // decimal digit terminates octal escape sequence
#pragma warning (disable:4800) // decimal digit terminates octal escape sequence
#endif
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: arkit_blend_shapes.proto
#ifndef GRPC_arkit_5fblend_5fshapes_2eproto__INCLUDED
#define GRPC_arkit_5fblend_5fshapes_2eproto__INCLUDED
#include "arkit_blend_shapes.pb.h"
#include <functional>
#include <grpc/impl/codegen/port_platform.h>
#include <grpcpp/impl/codegen/async_generic_service.h>
#include <grpcpp/impl/codegen/async_stream.h>
#include <grpcpp/impl/codegen/async_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/client_context.h>
#include <grpcpp/impl/codegen/completion_queue.h>
#include <grpcpp/impl/codegen/message_allocator.h>
#include <grpcpp/impl/codegen/method_handler.h>
#include <grpcpp/impl/codegen/proto_utils.h>
#include <grpcpp/impl/codegen/rpc_method.h>
#include <grpcpp/impl/codegen/server_callback.h>
#include <grpcpp/impl/codegen/server_callback_handlers.h>
#include <grpcpp/impl/codegen/server_context.h>
#include <grpcpp/impl/codegen/service_type.h>
#include <grpcpp/impl/codegen/status.h>
#include <grpcpp/impl/codegen/stub_options.h>
#include <grpcpp/impl/codegen/sync_stream.h>
namespace service {
} // namespace service
#endif // GRPC_arkit_5fblend_5fshapes_2eproto__INCLUDED
#else
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: arkit_blend_shapes.proto
#ifndef GRPC_arkit_5fblend_5fshapes_2eproto__INCLUDED
#define GRPC_arkit_5fblend_5fshapes_2eproto__INCLUDED
#include "arkit_blend_shapes.pb.h"
#include <functional>
#include <grpcpp/generic/async_generic_service.h>
#include <grpcpp/support/async_stream.h>
#include <grpcpp/support/async_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/client_context.h>
#include <grpcpp/impl/codegen/completion_queue.h>
#include <grpcpp/impl/codegen/message_allocator.h>
#include <grpcpp/impl/codegen/method_handler.h>
#include <grpcpp/impl/codegen/proto_utils.h>
#include <grpcpp/impl/codegen/rpc_method.h>
#include <grpcpp/impl/codegen/server_callback.h>
#include <grpcpp/impl/codegen/server_callback_handlers.h>
#include <grpcpp/impl/codegen/server_context.h>
#include <grpcpp/impl/codegen/service_type.h>
#include <grpcpp/impl/codegen/status.h>
#include <grpcpp/impl/codegen/stub_options.h>
#include <grpcpp/impl/codegen/sync_stream.h>
namespace service {
} // namespace service
#endif // GRPC_arkit_5fblend_5fshapes_2eproto__INCLUDED
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,552 @@
#if defined(_WIN32) || defined(__ANDROID__)
#if defined(_MSC_VER)
#pragma warning (disable:4018) // 'expression' : signed/unsigned mismatch
#pragma warning (disable:4065) // switch statement contains 'default' but no 'case' labels
#pragma warning (disable:4146) // unary minus operator applied to unsigned type, result still unsigned
#pragma warning (disable:4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data
#pragma warning (disable:4251) // 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
#pragma warning (disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
#pragma warning (disable:4305) // 'identifier' : truncation from 'type1' to 'type2'
#pragma warning (disable:4307) // 'operator' : integral constant overflow
#pragma warning (disable:4309) // 'conversion' : truncation of constant value
#pragma warning (disable:4334) // 'operator' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)
#pragma warning (disable:4355) // 'this' : used in base member initializer list
#pragma warning (disable:4506) // no definition for inline function 'function'
#pragma warning (disable:4996) // The compiler encountered a deprecated declaration.
#pragma warning (disable:4125) // decimal digit terminates octal escape sequence
#pragma warning (disable:4800) // decimal digit terminates octal escape sequence
#endif
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: service.proto
#include "service.pb.h"
#include "service.grpc.pb.h"
#include <functional>
#include <grpcpp/impl/codegen/async_stream.h>
#include <grpcpp/impl/codegen/async_unary_call.h>
#include <grpcpp/impl/codegen/channel_interface.h>
#include <grpcpp/impl/codegen/client_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/message_allocator.h>
#include <grpcpp/impl/codegen/method_handler.h>
#include <grpcpp/impl/codegen/rpc_service_method.h>
#include <grpcpp/impl/codegen/server_callback.h>
#include <grpcpp/impl/codegen/server_callback_handlers.h>
#include <grpcpp/impl/codegen/server_context.h>
#include <grpcpp/impl/codegen/service_type.h>
#include <grpcpp/impl/codegen/sync_stream.h>
namespace service {
static const char* ConvaiService_method_names[] = {
"/service.ConvaiService/Hello",
"/service.ConvaiService/HelloStream",
"/service.ConvaiService/SpeechToText",
"/service.ConvaiService/GetResponse",
"/service.ConvaiService/GetResponseSingle",
"/service.ConvaiService/SubmitFeedback",
};
std::unique_ptr< ConvaiService::Stub> ConvaiService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
(void)options;
std::unique_ptr< ConvaiService::Stub> stub(new ConvaiService::Stub(channel));
return stub;
}
ConvaiService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel)
: channel_(channel), rpcmethod_Hello_(ConvaiService_method_names[0], ::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_HelloStream_(ConvaiService_method_names[1], ::grpc::internal::RpcMethod::BIDI_STREAMING, channel)
, rpcmethod_SpeechToText_(ConvaiService_method_names[2], ::grpc::internal::RpcMethod::BIDI_STREAMING, channel)
, rpcmethod_GetResponse_(ConvaiService_method_names[3], ::grpc::internal::RpcMethod::BIDI_STREAMING, channel)
, rpcmethod_GetResponseSingle_(ConvaiService_method_names[4], ::grpc::internal::RpcMethod::SERVER_STREAMING, channel)
, rpcmethod_SubmitFeedback_(ConvaiService_method_names[5], ::grpc::internal::RpcMethod::NORMAL_RPC, channel)
{}
::grpc::Status ConvaiService::Stub::Hello(::grpc::ClientContext* context, const ::service::HelloRequest& request, ::service::HelloResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::service::HelloRequest, ::service::HelloResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Hello_, context, request, response);
}
void ConvaiService::Stub::experimental_async::Hello(::grpc::ClientContext* context, const ::service::HelloRequest* request, ::service::HelloResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::service::HelloRequest, ::service::HelloResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Hello_, context, request, response, std::move(f));
}
void ConvaiService::Stub::experimental_async::Hello(::grpc::ClientContext* context, const ::service::HelloRequest* request, ::service::HelloResponse* response, ::grpc::experimental::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Hello_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::service::HelloResponse>* ConvaiService::Stub::PrepareAsyncHelloRaw(::grpc::ClientContext* context, const ::service::HelloRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::service::HelloResponse, ::service::HelloRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Hello_, context, request);
}
::grpc::ClientAsyncResponseReader< ::service::HelloResponse>* ConvaiService::Stub::AsyncHelloRaw(::grpc::ClientContext* context, const ::service::HelloRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncHelloRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::ClientReaderWriter< ::service::HelloRequest, ::service::HelloResponse>* ConvaiService::Stub::HelloStreamRaw(::grpc::ClientContext* context) {
return ::grpc::internal::ClientReaderWriterFactory< ::service::HelloRequest, ::service::HelloResponse>::Create(channel_.get(), rpcmethod_HelloStream_, context);
}
void ConvaiService::Stub::experimental_async::HelloStream(::grpc::ClientContext* context, ::grpc::experimental::ClientBidiReactor< ::service::HelloRequest,::service::HelloResponse>* reactor) {
::grpc::internal::ClientCallbackReaderWriterFactory< ::service::HelloRequest,::service::HelloResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_HelloStream_, context, reactor);
}
::grpc::ClientAsyncReaderWriter< ::service::HelloRequest, ::service::HelloResponse>* ConvaiService::Stub::AsyncHelloStreamRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::HelloRequest, ::service::HelloResponse>::Create(channel_.get(), cq, rpcmethod_HelloStream_, context, true, tag);
}
::grpc::ClientAsyncReaderWriter< ::service::HelloRequest, ::service::HelloResponse>* ConvaiService::Stub::PrepareAsyncHelloStreamRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::HelloRequest, ::service::HelloResponse>::Create(channel_.get(), cq, rpcmethod_HelloStream_, context, false, nullptr);
}
::grpc::ClientReaderWriter< ::service::STTRequest, ::service::STTResponse>* ConvaiService::Stub::SpeechToTextRaw(::grpc::ClientContext* context) {
return ::grpc::internal::ClientReaderWriterFactory< ::service::STTRequest, ::service::STTResponse>::Create(channel_.get(), rpcmethod_SpeechToText_, context);
}
void ConvaiService::Stub::experimental_async::SpeechToText(::grpc::ClientContext* context, ::grpc::experimental::ClientBidiReactor< ::service::STTRequest,::service::STTResponse>* reactor) {
::grpc::internal::ClientCallbackReaderWriterFactory< ::service::STTRequest,::service::STTResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_SpeechToText_, context, reactor);
}
::grpc::ClientAsyncReaderWriter< ::service::STTRequest, ::service::STTResponse>* ConvaiService::Stub::AsyncSpeechToTextRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::STTRequest, ::service::STTResponse>::Create(channel_.get(), cq, rpcmethod_SpeechToText_, context, true, tag);
}
::grpc::ClientAsyncReaderWriter< ::service::STTRequest, ::service::STTResponse>* ConvaiService::Stub::PrepareAsyncSpeechToTextRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::STTRequest, ::service::STTResponse>::Create(channel_.get(), cq, rpcmethod_SpeechToText_, context, false, nullptr);
}
::grpc::ClientReaderWriter< ::service::GetResponseRequest, ::service::GetResponseResponse>* ConvaiService::Stub::GetResponseRaw(::grpc::ClientContext* context) {
return ::grpc::internal::ClientReaderWriterFactory< ::service::GetResponseRequest, ::service::GetResponseResponse>::Create(channel_.get(), rpcmethod_GetResponse_, context);
}
void ConvaiService::Stub::experimental_async::GetResponse(::grpc::ClientContext* context, ::grpc::experimental::ClientBidiReactor< ::service::GetResponseRequest,::service::GetResponseResponse>* reactor) {
::grpc::internal::ClientCallbackReaderWriterFactory< ::service::GetResponseRequest,::service::GetResponseResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_GetResponse_, context, reactor);
}
::grpc::ClientAsyncReaderWriter< ::service::GetResponseRequest, ::service::GetResponseResponse>* ConvaiService::Stub::AsyncGetResponseRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::GetResponseRequest, ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponse_, context, true, tag);
}
::grpc::ClientAsyncReaderWriter< ::service::GetResponseRequest, ::service::GetResponseResponse>* ConvaiService::Stub::PrepareAsyncGetResponseRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::GetResponseRequest, ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponse_, context, false, nullptr);
}
::grpc::ClientReader< ::service::GetResponseResponse>* ConvaiService::Stub::GetResponseSingleRaw(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle& request) {
return ::grpc::internal::ClientReaderFactory< ::service::GetResponseResponse>::Create(channel_.get(), rpcmethod_GetResponseSingle_, context, request);
}
void ConvaiService::Stub::experimental_async::GetResponseSingle(::grpc::ClientContext* context, ::service::GetResponseRequestSingle* request, ::grpc::experimental::ClientReadReactor< ::service::GetResponseResponse>* reactor) {
::grpc::internal::ClientCallbackReaderFactory< ::service::GetResponseResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_GetResponseSingle_, context, request, reactor);
}
::grpc::ClientAsyncReader< ::service::GetResponseResponse>* ConvaiService::Stub::AsyncGetResponseSingleRaw(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle& request, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderFactory< ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponseSingle_, context, request, true, tag);
}
::grpc::ClientAsyncReader< ::service::GetResponseResponse>* ConvaiService::Stub::PrepareAsyncGetResponseSingleRaw(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderFactory< ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponseSingle_, context, request, false, nullptr);
}
::grpc::Status ConvaiService::Stub::SubmitFeedback(::grpc::ClientContext* context, const ::service::FeedbackRequest& request, ::service::FeedbackResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::service::FeedbackRequest, ::service::FeedbackResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SubmitFeedback_, context, request, response);
}
void ConvaiService::Stub::experimental_async::SubmitFeedback(::grpc::ClientContext* context, const ::service::FeedbackRequest* request, ::service::FeedbackResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::service::FeedbackRequest, ::service::FeedbackResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SubmitFeedback_, context, request, response, std::move(f));
}
void ConvaiService::Stub::experimental_async::SubmitFeedback(::grpc::ClientContext* context, const ::service::FeedbackRequest* request, ::service::FeedbackResponse* response, ::grpc::experimental::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SubmitFeedback_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::service::FeedbackResponse>* ConvaiService::Stub::PrepareAsyncSubmitFeedbackRaw(::grpc::ClientContext* context, const ::service::FeedbackRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::service::FeedbackResponse, ::service::FeedbackRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SubmitFeedback_, context, request);
}
::grpc::ClientAsyncResponseReader< ::service::FeedbackResponse>* ConvaiService::Stub::AsyncSubmitFeedbackRaw(::grpc::ClientContext* context, const ::service::FeedbackRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSubmitFeedbackRaw(context, request, cq);
result->StartCall();
return result;
}
ConvaiService::Service::Service() {
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[0],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ConvaiService::Service, ::service::HelloRequest, ::service::HelloResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
const ::service::HelloRequest* req,
::service::HelloResponse* resp) {
return service->Hello(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[1],
::grpc::internal::RpcMethod::BIDI_STREAMING,
new ::grpc::internal::BidiStreamingHandler< ConvaiService::Service, ::service::HelloRequest, ::service::HelloResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
::grpc::ServerReaderWriter<::service::HelloResponse,
::service::HelloRequest>* stream) {
return service->HelloStream(ctx, stream);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[2],
::grpc::internal::RpcMethod::BIDI_STREAMING,
new ::grpc::internal::BidiStreamingHandler< ConvaiService::Service, ::service::STTRequest, ::service::STTResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
::grpc::ServerReaderWriter<::service::STTResponse,
::service::STTRequest>* stream) {
return service->SpeechToText(ctx, stream);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[3],
::grpc::internal::RpcMethod::BIDI_STREAMING,
new ::grpc::internal::BidiStreamingHandler< ConvaiService::Service, ::service::GetResponseRequest, ::service::GetResponseResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
::grpc::ServerReaderWriter<::service::GetResponseResponse,
::service::GetResponseRequest>* stream) {
return service->GetResponse(ctx, stream);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[4],
::grpc::internal::RpcMethod::SERVER_STREAMING,
new ::grpc::internal::ServerStreamingHandler< ConvaiService::Service, ::service::GetResponseRequestSingle, ::service::GetResponseResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
const ::service::GetResponseRequestSingle* req,
::grpc::ServerWriter<::service::GetResponseResponse>* writer) {
return service->GetResponseSingle(ctx, req, writer);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[5],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ConvaiService::Service, ::service::FeedbackRequest, ::service::FeedbackResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
const ::service::FeedbackRequest* req,
::service::FeedbackResponse* resp) {
return service->SubmitFeedback(ctx, req, resp);
}, this)));
}
ConvaiService::Service::~Service() {
}
::grpc::Status ConvaiService::Service::Hello(::grpc::ServerContext* context, const ::service::HelloRequest* request, ::service::HelloResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::HelloStream(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::service::HelloResponse, ::service::HelloRequest>* stream) {
(void) context;
(void) stream;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::SpeechToText(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::service::STTResponse, ::service::STTRequest>* stream) {
(void) context;
(void) stream;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::GetResponse(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::service::GetResponseResponse, ::service::GetResponseRequest>* stream) {
(void) context;
(void) stream;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::GetResponseSingle(::grpc::ServerContext* context, const ::service::GetResponseRequestSingle* request, ::grpc::ServerWriter< ::service::GetResponseResponse>* writer) {
(void) context;
(void) request;
(void) writer;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::SubmitFeedback(::grpc::ServerContext* context, const ::service::FeedbackRequest* request, ::service::FeedbackResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
} // namespace service
#else
// Generated by the gRPC C++ plugin.
// If you make any local change, they will be lost.
// source: service.proto
#include "service.pb.h"
#include "service.grpc.pb.h"
#include <functional>
#include <grpcpp/support/async_stream.h>
#include <grpcpp/support/async_unary_call.h>
#include <grpcpp/impl/codegen/channel_interface.h>
#include <grpcpp/impl/codegen/client_unary_call.h>
#include <grpcpp/impl/codegen/client_callback.h>
#include <grpcpp/impl/codegen/message_allocator.h>
#include <grpcpp/impl/codegen/method_handler.h>
#include <grpcpp/impl/codegen/rpc_service_method.h>
#include <grpcpp/impl/codegen/server_callback.h>
#include <grpcpp/impl/codegen/server_callback_handlers.h>
#include <grpcpp/impl/codegen/server_context.h>
#include <grpcpp/impl/codegen/service_type.h>
#include <grpcpp/impl/codegen/sync_stream.h>
namespace service {
static const char* ConvaiService_method_names[] = {
"/service.ConvaiService/Hello",
"/service.ConvaiService/HelloStream",
"/service.ConvaiService/SpeechToText",
"/service.ConvaiService/GetResponse",
"/service.ConvaiService/GetResponseSingle",
"/service.ConvaiService/SubmitFeedback",
};
std::unique_ptr< ConvaiService::Stub> ConvaiService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
(void)options;
std::unique_ptr< ConvaiService::Stub> stub(new ConvaiService::Stub(channel, options));
return stub;
}
ConvaiService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
: channel_(channel), rpcmethod_Hello_(ConvaiService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
, rpcmethod_HelloStream_(ConvaiService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::BIDI_STREAMING, channel)
, rpcmethod_SpeechToText_(ConvaiService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::BIDI_STREAMING, channel)
, rpcmethod_GetResponse_(ConvaiService_method_names[3], options.suffix_for_stats(),::grpc::internal::RpcMethod::BIDI_STREAMING, channel)
, rpcmethod_GetResponseSingle_(ConvaiService_method_names[4], options.suffix_for_stats(),::grpc::internal::RpcMethod::SERVER_STREAMING, channel)
, rpcmethod_SubmitFeedback_(ConvaiService_method_names[5], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
{}
::grpc::Status ConvaiService::Stub::Hello(::grpc::ClientContext* context, const ::service::HelloRequest& request, ::service::HelloResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::service::HelloRequest, ::service::HelloResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Hello_, context, request, response);
}
void ConvaiService::Stub::async::Hello(::grpc::ClientContext* context, const ::service::HelloRequest* request, ::service::HelloResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::service::HelloRequest, ::service::HelloResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Hello_, context, request, response, std::move(f));
}
void ConvaiService::Stub::async::Hello(::grpc::ClientContext* context, const ::service::HelloRequest* request, ::service::HelloResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Hello_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::service::HelloResponse>* ConvaiService::Stub::PrepareAsyncHelloRaw(::grpc::ClientContext* context, const ::service::HelloRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::service::HelloResponse, ::service::HelloRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Hello_, context, request);
}
::grpc::ClientAsyncResponseReader< ::service::HelloResponse>* ConvaiService::Stub::AsyncHelloRaw(::grpc::ClientContext* context, const ::service::HelloRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncHelloRaw(context, request, cq);
result->StartCall();
return result;
}
::grpc::ClientReaderWriter< ::service::HelloRequest, ::service::HelloResponse>* ConvaiService::Stub::HelloStreamRaw(::grpc::ClientContext* context) {
return ::grpc::internal::ClientReaderWriterFactory< ::service::HelloRequest, ::service::HelloResponse>::Create(channel_.get(), rpcmethod_HelloStream_, context);
}
void ConvaiService::Stub::async::HelloStream(::grpc::ClientContext* context, ::grpc::ClientBidiReactor< ::service::HelloRequest,::service::HelloResponse>* reactor) {
::grpc::internal::ClientCallbackReaderWriterFactory< ::service::HelloRequest,::service::HelloResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_HelloStream_, context, reactor);
}
::grpc::ClientAsyncReaderWriter< ::service::HelloRequest, ::service::HelloResponse>* ConvaiService::Stub::AsyncHelloStreamRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::HelloRequest, ::service::HelloResponse>::Create(channel_.get(), cq, rpcmethod_HelloStream_, context, true, tag);
}
::grpc::ClientAsyncReaderWriter< ::service::HelloRequest, ::service::HelloResponse>* ConvaiService::Stub::PrepareAsyncHelloStreamRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::HelloRequest, ::service::HelloResponse>::Create(channel_.get(), cq, rpcmethod_HelloStream_, context, false, nullptr);
}
::grpc::ClientReaderWriter< ::service::STTRequest, ::service::STTResponse>* ConvaiService::Stub::SpeechToTextRaw(::grpc::ClientContext* context) {
return ::grpc::internal::ClientReaderWriterFactory< ::service::STTRequest, ::service::STTResponse>::Create(channel_.get(), rpcmethod_SpeechToText_, context);
}
void ConvaiService::Stub::async::SpeechToText(::grpc::ClientContext* context, ::grpc::ClientBidiReactor< ::service::STTRequest,::service::STTResponse>* reactor) {
::grpc::internal::ClientCallbackReaderWriterFactory< ::service::STTRequest,::service::STTResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_SpeechToText_, context, reactor);
}
::grpc::ClientAsyncReaderWriter< ::service::STTRequest, ::service::STTResponse>* ConvaiService::Stub::AsyncSpeechToTextRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::STTRequest, ::service::STTResponse>::Create(channel_.get(), cq, rpcmethod_SpeechToText_, context, true, tag);
}
::grpc::ClientAsyncReaderWriter< ::service::STTRequest, ::service::STTResponse>* ConvaiService::Stub::PrepareAsyncSpeechToTextRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::STTRequest, ::service::STTResponse>::Create(channel_.get(), cq, rpcmethod_SpeechToText_, context, false, nullptr);
}
::grpc::ClientReaderWriter< ::service::GetResponseRequest, ::service::GetResponseResponse>* ConvaiService::Stub::GetResponseRaw(::grpc::ClientContext* context) {
return ::grpc::internal::ClientReaderWriterFactory< ::service::GetResponseRequest, ::service::GetResponseResponse>::Create(channel_.get(), rpcmethod_GetResponse_, context);
}
void ConvaiService::Stub::async::GetResponse(::grpc::ClientContext* context, ::grpc::ClientBidiReactor< ::service::GetResponseRequest,::service::GetResponseResponse>* reactor) {
::grpc::internal::ClientCallbackReaderWriterFactory< ::service::GetResponseRequest,::service::GetResponseResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_GetResponse_, context, reactor);
}
::grpc::ClientAsyncReaderWriter< ::service::GetResponseRequest, ::service::GetResponseResponse>* ConvaiService::Stub::AsyncGetResponseRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::GetResponseRequest, ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponse_, context, true, tag);
}
::grpc::ClientAsyncReaderWriter< ::service::GetResponseRequest, ::service::GetResponseResponse>* ConvaiService::Stub::PrepareAsyncGetResponseRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderWriterFactory< ::service::GetResponseRequest, ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponse_, context, false, nullptr);
}
::grpc::ClientReader< ::service::GetResponseResponse>* ConvaiService::Stub::GetResponseSingleRaw(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle& request) {
return ::grpc::internal::ClientReaderFactory< ::service::GetResponseResponse>::Create(channel_.get(), rpcmethod_GetResponseSingle_, context, request);
}
void ConvaiService::Stub::async::GetResponseSingle(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle* request, ::grpc::ClientReadReactor< ::service::GetResponseResponse>* reactor) {
::grpc::internal::ClientCallbackReaderFactory< ::service::GetResponseResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_GetResponseSingle_, context, request, reactor);
}
::grpc::ClientAsyncReader< ::service::GetResponseResponse>* ConvaiService::Stub::AsyncGetResponseSingleRaw(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle& request, ::grpc::CompletionQueue* cq, void* tag) {
return ::grpc::internal::ClientAsyncReaderFactory< ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponseSingle_, context, request, true, tag);
}
::grpc::ClientAsyncReader< ::service::GetResponseResponse>* ConvaiService::Stub::PrepareAsyncGetResponseSingleRaw(::grpc::ClientContext* context, const ::service::GetResponseRequestSingle& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncReaderFactory< ::service::GetResponseResponse>::Create(channel_.get(), cq, rpcmethod_GetResponseSingle_, context, request, false, nullptr);
}
::grpc::Status ConvaiService::Stub::SubmitFeedback(::grpc::ClientContext* context, const ::service::FeedbackRequest& request, ::service::FeedbackResponse* response) {
return ::grpc::internal::BlockingUnaryCall< ::service::FeedbackRequest, ::service::FeedbackResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SubmitFeedback_, context, request, response);
}
void ConvaiService::Stub::async::SubmitFeedback(::grpc::ClientContext* context, const ::service::FeedbackRequest* request, ::service::FeedbackResponse* response, std::function<void(::grpc::Status)> f) {
::grpc::internal::CallbackUnaryCall< ::service::FeedbackRequest, ::service::FeedbackResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SubmitFeedback_, context, request, response, std::move(f));
}
void ConvaiService::Stub::async::SubmitFeedback(::grpc::ClientContext* context, const ::service::FeedbackRequest* request, ::service::FeedbackResponse* response, ::grpc::ClientUnaryReactor* reactor) {
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SubmitFeedback_, context, request, response, reactor);
}
::grpc::ClientAsyncResponseReader< ::service::FeedbackResponse>* ConvaiService::Stub::PrepareAsyncSubmitFeedbackRaw(::grpc::ClientContext* context, const ::service::FeedbackRequest& request, ::grpc::CompletionQueue* cq) {
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::service::FeedbackResponse, ::service::FeedbackRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SubmitFeedback_, context, request);
}
::grpc::ClientAsyncResponseReader< ::service::FeedbackResponse>* ConvaiService::Stub::AsyncSubmitFeedbackRaw(::grpc::ClientContext* context, const ::service::FeedbackRequest& request, ::grpc::CompletionQueue* cq) {
auto* result =
this->PrepareAsyncSubmitFeedbackRaw(context, request, cq);
result->StartCall();
return result;
}
ConvaiService::Service::Service() {
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[0],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ConvaiService::Service, ::service::HelloRequest, ::service::HelloResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
const ::service::HelloRequest* req,
::service::HelloResponse* resp) {
return service->Hello(ctx, req, resp);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[1],
::grpc::internal::RpcMethod::BIDI_STREAMING,
new ::grpc::internal::BidiStreamingHandler< ConvaiService::Service, ::service::HelloRequest, ::service::HelloResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
::grpc::ServerReaderWriter<::service::HelloResponse,
::service::HelloRequest>* stream) {
return service->HelloStream(ctx, stream);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[2],
::grpc::internal::RpcMethod::BIDI_STREAMING,
new ::grpc::internal::BidiStreamingHandler< ConvaiService::Service, ::service::STTRequest, ::service::STTResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
::grpc::ServerReaderWriter<::service::STTResponse,
::service::STTRequest>* stream) {
return service->SpeechToText(ctx, stream);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[3],
::grpc::internal::RpcMethod::BIDI_STREAMING,
new ::grpc::internal::BidiStreamingHandler< ConvaiService::Service, ::service::GetResponseRequest, ::service::GetResponseResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
::grpc::ServerReaderWriter<::service::GetResponseResponse,
::service::GetResponseRequest>* stream) {
return service->GetResponse(ctx, stream);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[4],
::grpc::internal::RpcMethod::SERVER_STREAMING,
new ::grpc::internal::ServerStreamingHandler< ConvaiService::Service, ::service::GetResponseRequestSingle, ::service::GetResponseResponse>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
const ::service::GetResponseRequestSingle* req,
::grpc::ServerWriter<::service::GetResponseResponse>* writer) {
return service->GetResponseSingle(ctx, req, writer);
}, this)));
AddMethod(new ::grpc::internal::RpcServiceMethod(
ConvaiService_method_names[5],
::grpc::internal::RpcMethod::NORMAL_RPC,
new ::grpc::internal::RpcMethodHandler< ConvaiService::Service, ::service::FeedbackRequest, ::service::FeedbackResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
[](ConvaiService::Service* service,
::grpc::ServerContext* ctx,
const ::service::FeedbackRequest* req,
::service::FeedbackResponse* resp) {
return service->SubmitFeedback(ctx, req, resp);
}, this)));
}
ConvaiService::Service::~Service() {
}
::grpc::Status ConvaiService::Service::Hello(::grpc::ServerContext* context, const ::service::HelloRequest* request, ::service::HelloResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::HelloStream(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::service::HelloResponse, ::service::HelloRequest>* stream) {
(void) context;
(void) stream;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::SpeechToText(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::service::STTResponse, ::service::STTRequest>* stream) {
(void) context;
(void) stream;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::GetResponse(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::service::GetResponseResponse, ::service::GetResponseRequest>* stream) {
(void) context;
(void) stream;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::GetResponseSingle(::grpc::ServerContext* context, const ::service::GetResponseRequestSingle* request, ::grpc::ServerWriter< ::service::GetResponseResponse>* writer) {
(void) context;
(void) request;
(void) writer;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
::grpc::Status ConvaiService::Service::SubmitFeedback(::grpc::ServerContext* context, const ::service::FeedbackRequest* request, ::service::FeedbackResponse* response) {
(void) context;
(void) request;
(void) response;
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
}
} // namespace service
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
#include "RestAPI/ConvaiAPIBase.h"
#include "ConvaiUtils.h"
DEFINE_LOG_CATEGORY(ConvaiBaseHttpLogs);
void UConvaiAPIBaseProxy::Activate()
{
TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request = CONVAI_HTTP_MODULE::Get().CreateRequest();
if (!ConfigureRequest(Request, TEXT("")))
{
HandleFailure();
return;
}
Request->OnProcessRequestComplete().BindUObject(this, &ThisClass::OnHttpRequestComplete);
if (Request->ProcessRequest())
{
AddToRoot();
}
}
bool UConvaiAPIBaseProxy::ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb)
{
if (!UConvaiFormValidation::ValidateInputText(URL) || !UConvaiFormValidation::ValidateInputText(Verb))
{
HandleFailure();
return false;
}
TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
FString AuthKey = AuthHeaderAndKey.Value;
FString AuthHeader = AuthHeaderAndKey.Key;
if (!UConvaiFormValidation::ValidateAuthKey(AuthKey))
{
HandleFailure();
return false;
}
Request->SetURL(URL);
Request->SetVerb(Verb);
Request->SetHeader(AuthHeader, AuthKey);
// Child classes can add filed to this object
TSharedPtr<FJsonObject> ObjectToSend = MakeShareable(new FJsonObject);
if (AddContentToRequestAsString(ObjectToSend))
{
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
FString Content;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Content);
FJsonSerializer::Serialize(ObjectToSend.ToSharedRef(), Writer);
Request->SetContentAsString(Content);
return true;
}
CONVAI_HTTP_PAYLOAD_ARRAY_TYPE DataToSend;
FString Boundary = TEXT("ConvaiPluginFormBoundary") + FString::FromInt(FDateTime::Now().GetTicks());
if (AddContentToRequest(DataToSend, Boundary))
{
if (FString(Verb).Equals(ConvaiHttpConstants::PUT))
{
Request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream"));
Request->SetContent(DataToSend);
return true;
}
Request->SetHeader(TEXT("Content-Type"), FString::Printf(TEXT("multipart/form-data; boundary=----%s"), *Boundary));
// Add closing boundary
FString ClosingBoundary = FString::Printf(TEXT("\r\n------%s--\r\n"), *Boundary);
DataToSend.Append((uint8*)TCHAR_TO_UTF8(*ClosingBoundary), ClosingBoundary.Len());
// Set the request content and content length
Request->SetHeader(TEXT("Content-Length"), FString::FromInt(DataToSend.Num()));
Request->SetContent(DataToSend);
return true;
}
return true;
}
void UConvaiAPIBaseProxy::OnHttpRequestComplete(CONVAI_HTTP_REQUEST_PTR Request, CONVAI_HTTP_RESPONSE_PTR Response, bool bWasSuccessful)
{
if (!Response)
{
if (bWasSuccessful)
{
CONVAI_LOG(ConvaiBaseHttpLogs, Warning, TEXT("HTTP request succeded - But response pointer is invalid"));
}
else
{
CONVAI_LOG(ConvaiBaseHttpLogs, Warning, TEXT("HTTP request failed - Response pointer is invalid"));
}
HandleFailure();
return;
}
if (!bWasSuccessful || Response->GetResponseCode() < 200 || Response->GetResponseCode() > 299)
{
CONVAI_LOG(ConvaiBaseHttpLogs, Warning, TEXT("HTTP request failed with code %d, and with response:%s"), Response->GetResponseCode(), *Response->GetContentAsString());
HandleFailure();
return;
}
ResponseString = Response->GetContentAsString();
ResponseData = Response->GetContent();
HandleSuccess();
}
void UConvaiAPIBaseProxy::HandleSuccess()
{
RemoveFromRoot();
}
void UConvaiAPIBaseProxy::HandleFailure()
{
RemoveFromRoot();
}
// END Base api proxy
bool UConvaiAPITokenInBodyProxy::AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary)
{
const TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
const FString AuthKey = AuthHeaderAndKey.Value;
const FString AuthHeader = AuthHeaderAndKey.Key;
if (!UConvaiFormValidation::ValidateAuthKey(AuthKey))
{
HandleFailure();
return false;
}
if (AuthHeader == ConvaiConstants::Auth_Token_Header)
{
const FString ExpIdField = FString::Printf(TEXT("\r\n------%s\r\nContent-Disposition: form-data; name=\"experience_session_id\"\r\n\r\n%s"), *Boundary, *AuthKey);
DataToSend.Append((uint8*)TCHAR_TO_UTF8(*ExpIdField), ExpIdField.Len());
}
return true;
}
bool UConvaiAPITokenInBodyProxy::AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend)
{
const TPair<FString, FString> AuthHeaderAndKey = UConvaiUtils::GetAuthHeaderAndKey();
const FString AuthKey = AuthHeaderAndKey.Value;
const FString AuthHeader = AuthHeaderAndKey.Key;
if (!UConvaiFormValidation::ValidateAuthKey(AuthKey))
{
HandleFailure();
return false;
}
if (AuthHeader == ConvaiConstants::Auth_Token_Header)
{
ObjectToSend->SetStringField(TEXT("experience_session_id"), AuthKey);
}
return true;
}

View File

@@ -0,0 +1,328 @@
#include "RestAPI/ConvaiLTMProxy.h"
#include "RestAPI/ConvaiURL.h"
#include "ConvaiDefinitions.h"
#include "Utility/Log/ConvaiLogger.h"
DEFINE_LOG_CATEGORY(LTMHttpLogs);
UConvaiCreateSpeakerID* UConvaiCreateSpeakerID::ConvaiCreateSpeakerIDProxy(FString SpeakerName, FString DeviceId)
{
UConvaiCreateSpeakerID* Proxy = NewObject<UConvaiCreateSpeakerID>();
Proxy->URL = UConvaiURL::GetEndpoint(EConvaiEndpoint::NewSpeaker);
Proxy->AssociatedSpeakerName = SpeakerName;
Proxy->AssociatedDeviceId = DeviceId;
return Proxy;
}
bool UConvaiCreateSpeakerID::ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb)
{
if (!Super::ConfigureRequest(Request, ConvaiHttpConstants::POST))
{
return false;
}
return true;
}
bool UConvaiCreateSpeakerID::AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend)
{
if (AssociatedSpeakerName.IsEmpty())
{
CONVAI_LOG(LTMHttpLogs, Error, TEXT("Speaker name is empty"));
HandleFailure();
return false;
}
ObjectToSend->SetStringField(TEXT("name"), AssociatedSpeakerName);
if (!AssociatedDeviceId.IsEmpty())
{
ObjectToSend->SetStringField(TEXT("deviceId"), AssociatedDeviceId);
}
return true;
}
void UConvaiCreateSpeakerID::HandleSuccess()
{
Super::HandleSuccess();
TSharedPtr<FJsonObject> JsonObject;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseString);
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
JsonObject->TryGetStringField(TEXT("speaker_id"), AssociatedSpeakerInfo.SpeakerID);
JsonObject->TryGetStringField(TEXT("name"), AssociatedSpeakerInfo.Name);
JsonObject->TryGetStringField(TEXT("device_id"), AssociatedSpeakerInfo.DeviceID);
OnSuccess.Broadcast(AssociatedSpeakerInfo);
}
else
{
CONVAI_LOG(LTMHttpLogs, Error, TEXT("Parse Json failed"));
HandleFailure();
}
}
void UConvaiCreateSpeakerID::HandleFailure()
{
Super::HandleFailure();
OnFailure.Broadcast(FConvaiSpeakerInfo());
}
UConvaiListSpeakerID* UConvaiListSpeakerID::ConvaiListSpeakerIDProxy()
{
UConvaiListSpeakerID* Proxy = NewObject<UConvaiListSpeakerID>();
Proxy->URL = UConvaiURL::GetEndpoint(EConvaiEndpoint::SpeakerIDList);
return Proxy;
}
bool UConvaiListSpeakerID::ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb)
{
if (!Super::ConfigureRequest(Request, ConvaiHttpConstants::POST))
{
return false;
}
return true;
}
void UConvaiListSpeakerID::HandleSuccess()
{
Super::HandleSuccess();
TArray<FConvaiSpeakerInfo> SpeakerInfoArray;
if (UConvaiLTMUtils::ParseConvaiSpeakerInfoArray(ResponseString, SpeakerInfoArray))
{
OnSuccess.Broadcast(SpeakerInfoArray);
}
else
{
CONVAI_LOG(LTMHttpLogs, Error, TEXT("Parse speaker id failed"));
HandleFailure();
}
}
void UConvaiListSpeakerID::HandleFailure()
{
Super::HandleFailure();
OnFailure.Broadcast(TArray<FConvaiSpeakerInfo>());
}
UConvaiDeleteSpeakerID* UConvaiDeleteSpeakerID::ConvaiDeleteSpeakerIDProxy(FString SpeakerID)
{
UConvaiDeleteSpeakerID* Proxy = NewObject<UConvaiDeleteSpeakerID>();
Proxy->URL = UConvaiURL::GetEndpoint(EConvaiEndpoint::DeleteSpeakerID);
Proxy->AssociatedSpeakerID = SpeakerID;
return Proxy;
}
bool UConvaiDeleteSpeakerID::ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb)
{
if (!Super::ConfigureRequest(Request, ConvaiHttpConstants::POST))
{
return false;
}
return true;
}
bool UConvaiDeleteSpeakerID::AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend)
{
if (AssociatedSpeakerID.IsEmpty())
{
HandleFailure();
return false;
}
ObjectToSend->SetStringField(TEXT("speakerId"), AssociatedSpeakerID);
return true;
}
void UConvaiDeleteSpeakerID::HandleSuccess()
{
Super::HandleSuccess();
OnSuccess.Broadcast(ResponseString);
}
void UConvaiDeleteSpeakerID::HandleFailure()
{
Super::HandleFailure();
OnFailure.Broadcast(TEXT("Http req failed"));
}
UConvaiGetLTMStatus* UConvaiGetLTMStatus::ConvaiGetLTMStatusProxy(FString CharacterID)
{
UConvaiGetLTMStatus* Proxy = NewObject<UConvaiGetLTMStatus>();
Proxy->URL = UConvaiURL::GetEndpoint(EConvaiEndpoint::CharacterGet);
Proxy->AssociatedCharacterID = CharacterID;
return Proxy;
}
bool UConvaiGetLTMStatus::ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb)
{
if (!Super::ConfigureRequest(Request, ConvaiHttpConstants::POST))
{
return false;
}
return true;
}
bool UConvaiGetLTMStatus::AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend)
{
if (AssociatedCharacterID.IsEmpty())
{
HandleFailure();
return false;
}
ObjectToSend->SetStringField(TEXT("charID"), AssociatedCharacterID);
return true;
}
void UConvaiGetLTMStatus::HandleSuccess()
{
Super::HandleSuccess();
bool bEnabled;
if (UConvaiLTMUtils::GetLTMStatus(ResponseString, bEnabled))
{
OnSuccess.Broadcast(bEnabled);
}
else
{
CONVAI_LOG(LTMHttpLogs, Error, TEXT("GetLTMStatus failed"));
HandleFailure();
}
}
void UConvaiGetLTMStatus::HandleFailure()
{
Super::HandleFailure();
OnFailure.Broadcast(false);
}
UConvaiSetLTMStatus* UConvaiSetLTMStatus::ConvaiSetLTMStatusProxy(FString CharacterID, bool bEnable)
{
UConvaiSetLTMStatus* Proxy = NewObject<UConvaiSetLTMStatus>();
Proxy->URL = UConvaiURL::GetEndpoint(EConvaiEndpoint::CharacterUpdate);
Proxy->AssociatedCharacterID = CharacterID;
Proxy->bAssociatedEnable = bEnable;
return Proxy;
}
bool UConvaiSetLTMStatus::ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb)
{
if (!Super::ConfigureRequest(Request, ConvaiHttpConstants::POST))
{
return false;
}
return true;
}
bool UConvaiSetLTMStatus::AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend)
{
if (AssociatedCharacterID.IsEmpty())
{
HandleFailure();
return false;
}
ObjectToSend->SetStringField(TEXT("charID"), AssociatedCharacterID);
TSharedPtr<FJsonObject> MemorySettings = MakeShared<FJsonObject>();
MemorySettings->SetBoolField(TEXT("enabled"), bAssociatedEnable);
ObjectToSend->SetObjectField(TEXT("memorySettings"), MemorySettings);
return true;
}
void UConvaiSetLTMStatus::HandleSuccess()
{
Super::HandleSuccess();
OnSuccess.Broadcast(ResponseString);
}
void UConvaiSetLTMStatus::HandleFailure()
{
Super::HandleFailure();
OnFailure.Broadcast(ResponseString);
}
bool UConvaiLTMUtils::ParseConvaiSpeakerInfoArray(const FString& JsonString, TArray<FConvaiSpeakerInfo>& OutSpeakerInfoArray)
{
OutSpeakerInfoArray.Empty();
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
TArray<TSharedPtr<FJsonValue>> JsonArray;
if (FJsonSerializer::Deserialize(Reader, JsonArray))
{
for (const TSharedPtr<FJsonValue>& JsonValue : JsonArray)
{
TSharedPtr<FJsonObject> JsonObject = JsonValue->AsObject();
if (JsonObject.IsValid())
{
FConvaiSpeakerInfo SpeakerInfo;
JsonObject->TryGetStringField(TEXT("speaker_id"), SpeakerInfo.SpeakerID);
JsonObject->TryGetStringField(TEXT("name"), SpeakerInfo.Name);
JsonObject->TryGetStringField(TEXT("device_id"), SpeakerInfo.DeviceID);
OutSpeakerInfoArray.Add(SpeakerInfo);
}
}
return true;
}
return false;
}
bool UConvaiLTMUtils::GetLTMStatus(const FString& JsonString, bool& bEnabled)
{
bEnabled = false;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
TSharedPtr<FJsonObject> JsonObject;
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
if (JsonObject->HasField(TEXT("memory_settings")))
{
TSharedPtr<FJsonObject> MemorySettingsObject = JsonObject->GetObjectField(TEXT("memory_settings"));
if (MemorySettingsObject.IsValid() && MemorySettingsObject->HasField(TEXT("enabled")))
{
bEnabled = MemorySettingsObject->GetBoolField(TEXT("enabled"));
return true;
}
}
}
return false;
}

View File

@@ -0,0 +1,157 @@
#include "RestAPI/ConvaiURL.h"
#include "Misc/CommandLine.h"
#include "../Convai.h"
#include "Utility/Log/ConvaiLogger.h"
// Define static members
const TCHAR UConvaiURL::BETA_SUBDOMAIN[] = TEXT("beta");
const TCHAR UConvaiURL::PROD_SUBDOMAIN[] = TEXT("api");
const TCHAR UConvaiURL::BASE_URL[] = TEXT("https://%s.convai.com/");
const TCHAR UConvaiURL::BASE_URL_FORMAT[] = TEXT("https://{0}.convai.com/");
const TCHAR UConvaiURL::LTM_SUBDOMAIN[] = TEXT("user/speaker/");
const TCHAR UConvaiURL::USER_SUBDOMAIN[] = TEXT("user/");
const TCHAR UConvaiURL::CHARACTER_SUBDOMAIN[] = TEXT("character/");
const TCHAR UConvaiURL::NARRATIVE_DESIGN_SUBDOMAIN[] = TEXT("character/narrative/");
TArray<EConvaiEndpoint> UConvaiURL::BetaEndpoints;
FString UConvaiURL::CustomBetaBaseURL = TEXT("");
FString UConvaiURL::CustomProdBaseURL = TEXT("");
bool UConvaiURL::bURLConfigInitialized = false;
void UConvaiURL::InitializeURLConfig()
{
if (bURLConfigInitialized)
{
return;
}
// First check settings
FString SettingsBetaURL = Convai::Get().GetConvaiSettings()->CustomBetaURL;
SettingsBetaURL.TrimEndInline();
SettingsBetaURL.TrimStartInline();
if (!SettingsBetaURL.IsEmpty())
{
CustomBetaBaseURL = SettingsBetaURL;
CONVAI_LOG(LogTemp, Log, TEXT("Using beta URL from settings: %s"), *CustomBetaBaseURL);
}
FString SettingsProdURL = Convai::Get().GetConvaiSettings()->CustomProdURL;
SettingsProdURL.TrimEndInline();
SettingsProdURL.TrimStartInline();
if (!SettingsProdURL.IsEmpty())
{
CustomProdBaseURL = SettingsProdURL;
CONVAI_LOG(LogTemp, Log, TEXT("Using prod URL from settings: %s"), *CustomProdBaseURL);
}
// Then check command line parameters (these will override settings if present)
FString BetaURL;
if (FParse::Value(FCommandLine::Get(), TEXT("ConvaiBetaURL="), BetaURL))
{
CustomBetaBaseURL = BetaURL;
CONVAI_LOG(LogTemp, Log, TEXT("Using custom beta URL from command line: %s"), *CustomBetaBaseURL);
}
FString ProdURL;
if (FParse::Value(FCommandLine::Get(), TEXT("ConvaiProdURL="), ProdURL))
{
CustomProdBaseURL = ProdURL;
CONVAI_LOG(LogTemp, Log, TEXT("Using custom prod URL from command line: %s"), *CustomProdBaseURL);
}
bURLConfigInitialized = true;
}
FString UConvaiURL::GetBaseURL(const bool bUseBeta)
{
InitializeURLConfig();
if (bUseBeta)
{
if (!CustomBetaBaseURL.IsEmpty())
{
return CustomBetaBaseURL;
}
return TEXT("https://beta.convai.com");
}
else
{
if (!CustomProdBaseURL.IsEmpty())
{
return CustomProdBaseURL;
}
return TEXT("https://api.convai.com");
}
}
FString UConvaiURL::GetFullURL(const FString& ApiPath, const bool bUseBeta)
{
FString BaseURL = GetBaseURL(bUseBeta);
// Ensure the base URL ends with a slash and the API path doesn't start with one
if (!BaseURL.EndsWith(TEXT("/")))
{
BaseURL += TEXT("/");
}
FString Path = ApiPath;
if (Path.StartsWith(TEXT("/")))
{
Path = Path.RightChop(1);
}
return BaseURL + Path;
}
FString UConvaiURL::GetFormattedBaseURL(const FString& Subdomain)
{
return FString::Format(BASE_URL_FORMAT, { Subdomain });
}
FString UConvaiURL::GetEndpoint(const EConvaiEndpoint Endpoint)
{
FString Api;
switch (Endpoint)
{
case EConvaiEndpoint::NewSpeaker:
Api = FString(LTM_SUBDOMAIN) + TEXT("new");
break;
case EConvaiEndpoint::SpeakerIDList:
Api = FString(LTM_SUBDOMAIN) + TEXT("list");
break;
case EConvaiEndpoint::DeleteSpeakerID:
Api = FString(LTM_SUBDOMAIN) + TEXT("delete");
break;
case EConvaiEndpoint::ReferralSourceStatus:
Api = FString(USER_SUBDOMAIN) + TEXT("referral-source-status");
break;
case EConvaiEndpoint::UpdateReferralSource:
Api = FString(USER_SUBDOMAIN) + TEXT("update-source");
break;
case EConvaiEndpoint::UserAPIUsage:
Api = FString(USER_SUBDOMAIN) + TEXT("user-api-usage");
break;
case EConvaiEndpoint::CharacterUpdate:
Api = FString(CHARACTER_SUBDOMAIN) + TEXT("update");
break;
case EConvaiEndpoint::CharacterGet:
Api = FString(CHARACTER_SUBDOMAIN) + TEXT("get");
break;
case EConvaiEndpoint::ListCharacterSections:
Api = FString(NARRATIVE_DESIGN_SUBDOMAIN) + TEXT("list-sections");
break;
case EConvaiEndpoint::ListCharacterTriggers:
Api = FString(NARRATIVE_DESIGN_SUBDOMAIN) + TEXT("list-triggers");
break;
default:
CONVAI_LOG(LogTemp, Warning, TEXT("Invalid endpoint!"));
return FString();
}
const bool bOnProd = !BetaEndpoints.Contains(Endpoint);
const FString Subdomain = bOnProd ? FString(PROD_SUBDOMAIN) : FString(BETA_SUBDOMAIN);
return GetFormattedBaseURL(Subdomain) + Api;
}

View File

@@ -0,0 +1,196 @@
#include "Utility/Log/ConvaiLogger.h"
#include "Misc/DateTime.h"
#include "Misc/Paths.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/PlatformFile.h"
#include "Misc/App.h"
#include "HAL/PlatformProcess.h" // for FPlatformProcess
#include "HAL/Event.h" // for FEvent methods
FConvaiLogger &FConvaiLogger::Get()
{
static FConvaiLogger Instance;
return Instance;
}
FConvaiLogger::FConvaiLogger()
: Thread(nullptr), WakeEvent(FPlatformProcess::GetSynchEventFromPool(false)), bStopping(false)
{
StartThread();
}
FConvaiLogger::~FConvaiLogger()
{
ShutdownThread();
if (WakeEvent)
{
FPlatformProcess::ReturnSynchEventToPool(WakeEvent);
}
}
void FConvaiLogger::StartThread()
{
// Logs go in "<ProjectDir>/Saved/ConvaiLogs"
const FString LogDir = FPaths::Combine(
FPaths::ProjectDir(),
TEXT("Saved"),
TEXT("ConvaiLogs"));
IPlatformFile &Plat = FPlatformFileManager::Get().GetPlatformFile();
Plat.CreateDirectoryTree(*LogDir);
FString PixelStreamingPort=TEXT("Default");
FParse::Value(FCommandLine::Get(), TEXT("PixelStreamingPort="), PixelStreamingPort);
// Build base filename: ProjectName_YYYYMMDD_HHMMSS[_Port]
const FString Timestamp = FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"));
FString BaseName = FString::Printf(TEXT("%s_%s"),
FApp::GetProjectName(),
*Timestamp);
if (!PixelStreamingPort.IsEmpty())
{
BaseName += FString::Printf(TEXT("_%s"), *PixelStreamingPort);
}
// 2) Ensure uniqueness by appending _1, _2, ... if file already exists
FString FileName = BaseName + TEXT(".log");
FString CandidatePath = FPaths::Combine(LogDir, FileName);
int32 Suffix = 1;
while (Plat.FileExists(*CandidatePath))
{
FileName = FString::Printf(TEXT("%s_%d.log"), *BaseName, Suffix++);
CandidatePath = FPaths::Combine(LogDir, FileName);
}
// Finally assign the unique path
LogFilePath = CandidatePath;
// Start the logger thread as before
Thread = FRunnableThread::Create(
this,
TEXT("ConvaiLoggerThread"),
0,
TPri_BelowNormal);
}
void FConvaiLogger::ShutdownThread()
{
bStopping = true;
if (WakeEvent)
WakeEvent->Trigger();
if (Thread)
{
Thread->WaitForCompletion();
delete Thread;
Thread = nullptr;
}
}
uint32 FConvaiLogger::Run()
{
while (!bStopping)
{
WakeEvent->Wait(500);
TArray<FString> Batch;
FString Msg;
while (MessageQueue.Dequeue(Msg))
{
Batch.Add(Msg);
}
if (Batch.Num())
{
FString Combined = FString::Join(Batch, TEXT("\n")) + TEXT("\n");
IPlatformFile &Plat = FPlatformFileManager::Get().GetPlatformFile();
if (IFileHandle *Handle = Plat.OpenWrite(*LogFilePath, /*bAppend=*/true))
{
FTCHARToUTF8 Converter(*Combined);
Handle->Write(reinterpret_cast<const uint8 *>(Converter.Get()), Converter.Length());
Handle->Flush();
delete Handle;
}
}
}
{
TArray<FString> FinalBatch;
FString Rem;
while (MessageQueue.Dequeue(Rem))
{
FinalBatch.Add(Rem);
}
if (FinalBatch.Num())
{
const FString Combined = FString::Join(FinalBatch, TEXT("\n")) + TEXT("\n");
IPlatformFile &Plat = FPlatformFileManager::Get().GetPlatformFile();
if (IFileHandle *Handle = Plat.OpenWrite(*LogFilePath, /*bAppend=*/true))
{
const FTCHARToUTF8 Converter(*Combined);
Handle->Write(reinterpret_cast<const uint8 *>(Converter.Get()), Converter.Length());
Handle->Flush();
delete Handle;
}
}
}
return 0;
}
void FConvaiLogger::Stop()
{
bStopping = true;
}
void FConvaiLogger::Log(const FString &Message)
{
const FString Formatted = FString::Printf(
TEXT("[%s] %s"),
*FDateTime::Now().ToString(TEXT("%H:%M:%S")),
*Message);
MessageQueue.Enqueue(Formatted);
if (WakeEvent)
WakeEvent->Trigger();
}
void UConvaiBlueprintLogger::C_ConvaiLog(UObject *WorldContextObject, EC_LogLevel Verbosity, const FString &Message)
{
const FString ContextName = WorldContextObject
? WorldContextObject->GetName()
: TEXT("UnknownContext");
const UEnum *EnumPtr = StaticEnum<EC_LogLevel>();
const FString VerbName = EnumPtr
? EnumPtr->GetNameStringByValue(static_cast<int64>(Verbosity))
: TEXT("UnknownVerbosity");
const FString FullMessage = FString::Printf(
TEXT("%s : %s : %s"),
*ContextName, *VerbName, *Message);
switch (Verbosity)
{
case EC_LogLevel::Verbose:
UE_LOG(LogTemp, Verbose, TEXT("%s"), *FullMessage);
break;
case EC_LogLevel::Log:
UE_LOG(LogTemp, Log, TEXT("%s"), *FullMessage);
break;
case EC_LogLevel::Warning:
UE_LOG(LogTemp, Warning, TEXT("%s"), *FullMessage);
break;
case EC_LogLevel::Error:
UE_LOG(LogTemp, Error, TEXT("%s"), *FullMessage);
break;
case EC_LogLevel::Fatal:
UE_LOG(LogTemp, Fatal, TEXT("%s"), *FullMessage);
break;
default:
UE_LOG(LogTemp, Log, TEXT("%s"), *FullMessage);
break;
}
FConvaiLogger::Get().Log(FullMessage);
}

View File

@@ -0,0 +1,32 @@
#pragma warning (disable:4668) // 'expression' : signed/unsigned mismatch
#ifdef PLATFORM_LINUX
#if PLATFORM_LINUX
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int getentropy(void *buffer, size_t length) {
if (length > 256) {
errno = EIO;
return -1;
}
int fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
return -1;
}
ssize_t result = read(fd, buffer, length);
int saved_errno = errno;
close(fd);
if (result != (ssize_t)length) {
errno = saved_errno;
return -1;
}
return 0;
}
#endif
#endif

View File

@@ -0,0 +1,40 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ConvaiDefinitions.h"
#include "ConvaiActionUtils.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiActionUtilsLog, Log, All);
class UConvaiEnvironment;
struct FConvaiResultAction;
struct FConvaiObjectEntry;
UCLASS()
class UConvaiActions : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// Function to split a string based on commas, but ignoring commas within quotes
static TArray<FString> SmartSplit(const FString& SequenceString);
// Extract text from an action result (e.g. Says "I love AI" -> I love AI)
static FString ExtractText(FString Action, FString ActionResult);
// Extract number from an action result (e.g. Waits for 5 seconds -> 5)
static float ExtractNumber(FString ActionResult);
static FString FindAction(FString ActionToBeParsed, TArray<FString> Actions);
// Removes inner descriptions from a string e.g. (Waits for <time in seconds> becomes Waits for)
static FString RemoveDesc(FString str);
static bool ParseAction(UConvaiEnvironment* Environment, FString ActionToBeParsed, FConvaiResultAction& ConvaiResultAction);
static bool ValidateEnvironment(UConvaiEnvironment* Environment, FString& Error);
};

View File

@@ -0,0 +1,25 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ConvaiAndroid.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiAndroidLog, Log, All);
UCLASS()
class UConvaiAndroid : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Convai|Android|Permissions")
static void ConvaiAndroidAskMicrophonePermission();
UFUNCTION(BlueprintCallable, Category = "Convai|Android|Permissions")
static bool ConvaiAndroidHasMicrophonePermission();
};

View File

@@ -0,0 +1,147 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AudioCapture.h"
#include "Components/SynthComponent.h"
#include "AudioCaptureCore.h"
#include "AudioCaptureDeviceInterface.h"
#include "ConvaiAudioCaptureComponent.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiAudioLog, Log, All);
namespace Audio
{
/** Class which contains an FAudioCapture object and performs analysis on the audio stream, only outputing audio if it matches a detection criteria. */
class FConvaiAudioCaptureSynth
{
public:
FConvaiAudioCaptureSynth();
virtual ~FConvaiAudioCaptureSynth();
TArray<FCaptureDeviceInfo> GetCaptureDevicesAvailable();
int32 MapInputDeviceIDToDeviceID(int32 InputDeviceID);
// Gets the default capture device info
bool GetDefaultCaptureDeviceInfo(FCaptureDeviceInfo& OutInfo);
bool GetCaptureDeviceInfo(FCaptureDeviceInfo& OutInfo, int32 DeviceIndex);
// Opens up a stream to the default capture device
bool OpenDefaultStream();
bool OpenStream(int32 DeviceIndex);
void CloseStream();
// Starts capturing audio
bool StartCapturing();
// Stops capturing audio
void StopCapturing();
// Immediately stop capturing audio
void AbortCapturing();
// Returned if the capture synth is closed
bool IsStreamOpen() const;
// Returns true if the capture synth is capturing audio
bool IsCapturing() const;
// Retrieves audio data from the capture synth.
// This returns audio only if there was non-zero audio since this function was last called.
bool GetAudioData(TArray<float>& OutAudioData);
// Returns the number of samples enqueued in the capture synth
int32 GetNumSamplesEnqueued();
FAudioCapture* GetAudioCapture();
private:
// Number of samples enqueued
int32 NumSamplesEnqueued;
// Information about the default capture device we're going to use
FCaptureDeviceInfo CaptureInfo;
// Audio capture object dealing with getting audio callbacks
FAudioCapture AudioCapture;
// Critical section to prevent reading and writing from the captured buffer at the same time
FCriticalSection CaptureCriticalSection;
// Buffer of audio capture data, yet to be copied to the output
TArray<float> AudioCaptureData;
// If the object has been initialized
bool bInitialized;
// If we're capturing data
bool bIsCapturing;
};
};
UCLASS(ClassGroup = Synth, meta = (BlueprintSpawnableComponent))
class UConvaiAudioCaptureComponent : public USynthComponent
{
GENERATED_BODY()
protected:
UConvaiAudioCaptureComponent(const FObjectInitializer& ObjectInitializer);
//~ Begin USynthComponent interface
virtual bool Init(int32& SampleRate) override;
virtual int32 OnGenerateAudio(float* OutAudio, int32 NumSamples) override;
virtual void OnBeginGenerate() override;
virtual void OnEndGenerate() override;
//~ End USynthComponent interface
//~ Begin UObject interface
virtual void BeginDestroy();
virtual bool IsReadyForFinishDestroy() override;
virtual void FinishDestroy() override;
//~ End UObject interface
public:
/**
* Induced latency in audio frames to use to account for jitter between mic capture hardware and audio render hardware.
* Increasing this number will increase latency but reduce potential for underruns.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Latency", meta = (ClampMin = "0", ClampMax = "1024"))
int32 JitterLatencyFrames;
bool GetDefaultCaptureDeviceInfo(Audio::FCaptureDeviceInfo& OutInfo);
bool GetCaptureDeviceInfo(Audio::FCaptureDeviceInfo& OutInfo, int32 DeviceIndex);
TArray<Audio::FCaptureDeviceInfo> GetCaptureDevicesAvailable();
int32 GetActiveCaptureDevice(Audio::FCaptureDeviceInfo& OutInfo);
bool SetCaptureDevice(int32 DeviceIndex);
Audio::FConvaiAudioCaptureSynth* GetCaptureSynth();
private:
int32 SelectedDeviceIndex;
Audio::FConvaiAudioCaptureSynth CaptureSynth;
TArray<float> CaptureAudioData;
int32 CapturedAudioDataSamples;
bool bSuccessfullyInitialized;
bool bIsCapturing;
bool bIsStreamOpen;
int32 CaptureChannels;
int32 FramesSinceStarting;
int32 ReadSampleIndex;
FThreadSafeBool bIsDestroying;
FThreadSafeBool bIsNotReadyForForFinishDestroy;
};

View File

@@ -0,0 +1,659 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
//#include "CoreMinimal.h"
// #undef UpdateResource
#include "Components/AudioComponent.h"
#include "RingBuffer.h"
#include "ConvaiDefinitions.h"
#include "Misc/ScopeLock.h"
#include "Interfaces/VoiceCodec.h"
#include "CoreTypes.h"
#include "Templates/UnrealTemplate.h"
#include "HAL/PlatformAtomics.h"
#include "HAL/PlatformMisc.h"
#include "ConvaiAudioStreamer.generated.h"
#define NUM_ENTROPY_VALUES 5
DECLARE_LOG_CATEGORY_EXTERN(ConvaiAudioStreamerLog, Log, All);
class USoundWaveProcedural;
class IConvaiLipSyncInterface;
class IConvaiLipSyncExtendedInterface;
class IConvaiVisionInterface;
/**
* Template for queues.
*
* This template implements an unbounded non-intrusive queue using a lock-free linked
* list that stores copies of the queued items. The template can operate in two modes:
* Multiple-producers single-consumer (MPSC) and Single-producer single-consumer (SPSC).
*
* The queue is thread-safe in both modes. The Dequeue() method ensures thread-safety by
* writing it in a way that does not depend on possible instruction reordering on the CPU.
* The Enqueue() method uses an atomic compare-and-swap in multiple-producers scenarios.
*
* @param T The type of items stored in the queue.
* @param Mode The queue mode (single-producer, single-consumer by default).
* @todo gmp: Implement node pooling.
*/
template<typename T, EQueueMode Mode = EQueueMode::Spsc>
class TConvaiQueue
{
public:
using FElementType = T;
/** Default constructor. */
TConvaiQueue()
{
Head = Tail = new TNode();
}
/** Destructor. */
~TConvaiQueue()
{
while (Tail != nullptr)
{
TNode* Node = Tail;
Tail = Tail->NextNode;
delete Node;
}
}
/**
* Removes and returns the item from the tail of the queue.
*
* @param OutValue Will hold the returned value.
* @return true if a value was returned, false if the queue was empty.
* @note To be called only from consumer thread.
* @see Empty, Enqueue, IsEmpty, Peek, Pop
*/
bool Dequeue(FElementType& OutItem)
{
TNode* Popped = Tail->NextNode;
if (Popped == nullptr)
{
return false;
}
TSAN_AFTER(&Tail->NextNode);
OutItem = MoveTemp(Popped->Item);
TNode* OldTail = Tail;
Tail = Popped;
Tail->Item = FElementType();
delete OldTail;
return true;
}
/**
* Empty the queue, discarding all items.
*
* @note To be called only from consumer thread.
* @see Dequeue, IsEmpty, Peek, Pop
*/
void Empty()
{
while (Pop());
}
/**
* Adds an item to the head of the queue.
*
* @param Item The item to add.
* @return true if the item was added, false otherwise.
* @note To be called only from producer thread(s).
* @see Dequeue, Pop
*/
bool Enqueue(const FElementType& Item)
{
TNode* NewNode = new TNode(Item);
if (NewNode == nullptr)
{
return false;
}
TNode* OldHead;
if (Mode == EQueueMode::Mpsc)
{
OldHead = (TNode*)FPlatformAtomics::InterlockedExchangePtr((void**)&Head, NewNode);
TSAN_BEFORE(&OldHead->NextNode);
FPlatformAtomics::InterlockedExchangePtr((void**)&OldHead->NextNode, NewNode);
}
else
{
OldHead = Head;
Head = NewNode;
TSAN_BEFORE(&OldHead->NextNode);
FPlatformMisc::MemoryBarrier();
OldHead->NextNode = NewNode;
}
return true;
}
/**
* Adds an item to the head of the queue.
*
* @param Item The item to add.
* @return true if the item was added, false otherwise.
* @note To be called only from producer thread(s).
* @see Dequeue, Pop
*/
bool Enqueue(FElementType&& Item)
{
TNode* NewNode = new TNode(MoveTemp(Item));
if (NewNode == nullptr)
{
return false;
}
TNode* OldHead;
if (Mode == EQueueMode::Mpsc)
{
OldHead = (TNode*)FPlatformAtomics::InterlockedExchangePtr((void**)&Head, NewNode);
TSAN_BEFORE(&OldHead->NextNode);
FPlatformAtomics::InterlockedExchangePtr((void**)&OldHead->NextNode, NewNode);
}
else
{
OldHead = Head;
Head = NewNode;
TSAN_BEFORE(&OldHead->NextNode);
FPlatformMisc::MemoryBarrier();
OldHead->NextNode = NewNode;
}
return true;
}
/**
* Checks whether the queue is empty.
*
* @return true if the queue is empty, false otherwise.
* @note To be called only from consumer thread.
* @see Dequeue, Empty, Peek, Pop
*/
bool IsEmpty() const
{
return (Tail->NextNode == nullptr);
}
/**
* Peeks at the queue's tail item without removing it.
*
* @param OutItem Will hold the peeked at item.
* @return true if an item was returned, false if the queue was empty.
* @note To be called only from consumer thread.
* @see Dequeue, Empty, IsEmpty, Pop
*/
bool Peek(FElementType& OutItem) const
{
if (Tail->NextNode == nullptr)
{
return false;
}
OutItem = Tail->NextNode->Item;
return true;
}
/**
* Peek at the queue's tail item without removing it.
*
* This version of Peek allows peeking at a queue of items that do not allow
* copying, such as TUniquePtr.
*
* @return Pointer to the item, or nullptr if queue is empty
*/
FElementType* Peek()
{
if (Tail->NextNode == nullptr)
{
return nullptr;
}
return &Tail->NextNode->Item;
}
/**
* Peek at the queue's head item
*
*
* @return Pointer to the item, or nullptr if queue is empty
*/
FElementType* PeekHead()
{
if (Tail->NextNode == nullptr)
{
return nullptr;
}
return &Head->Item;
}
FORCEINLINE const FElementType* Peek() const
{
return const_cast<TConvaiQueue*>(this)->Peek();
}
/**
* Removes the item from the tail of the queue.
*
* @return true if a value was removed, false if the queue was empty.
* @note To be called only from consumer thread.
* @see Dequeue, Empty, Enqueue, IsEmpty, Peek
*/
bool Pop()
{
TNode* Popped = Tail->NextNode;
if (Popped == nullptr)
{
return false;
}
TSAN_AFTER(&Tail->NextNode);
TNode* OldTail = Tail;
Tail = Popped;
Tail->Item = FElementType();
delete OldTail;
return true;
}
private:
/** Structure for the internal linked list. */
struct TNode
{
/** Holds a pointer to the next node in the list. */
TNode* volatile NextNode;
/** Holds the node's item. */
FElementType Item;
/** Default constructor. */
TNode()
: NextNode(nullptr)
{ }
/** Creates and initializes a new node. */
explicit TNode(const FElementType& InItem)
: NextNode(nullptr)
, Item(InItem)
{ }
/** Creates and initializes a new node. */
explicit TNode(FElementType&& InItem)
: NextNode(nullptr)
, Item(MoveTemp(InItem))
{ }
};
/** Holds a pointer to the head of the list. */
MS_ALIGN(16) TNode* volatile Head GCC_ALIGN(16);
/** Holds a pointer to the tail of the list. */
TNode* Tail;
private:
/** Hidden copy constructor. */
TConvaiQueue(const TConvaiQueue&) = delete;
/** Hidden assignment operator. */
TConvaiQueue& operator=(const TConvaiQueue&) = delete;
};
UCLASS()
class UConvaiAudioStreamer : public UAudioComponent
{
GENERATED_UCLASS_BODY()
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(FOnStartedTalkingSignature, UConvaiAudioStreamer, OnStartedTalking);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(FOnFinishedTalkingSignature, UConvaiAudioStreamer, OnFinishedTalking);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(FOVRLipSyncVisemesDataReadySignature, UConvaiAudioStreamer, OnVisemesReady);
public:
/** Send the encoded audio from the server to all clients (including the server again) */
UFUNCTION(NetMulticast, Reliable, Category = "VoiceNetworking")
void BroadcastVoiceDataToClients(TArray<uint8> const& EncodedVoiceData, uint32 SampleRate, uint32 NumChannels, uint32 SizeBeforeEncode);
/** Send the encoded audio from a client(/server) to the server, it should call at the end BroadcastVoiceDataToClients() */
UFUNCTION(Server, Reliable, Category = "VoiceNetworking")
virtual void ProcessEncodedVoiceData(TArray<uint8> const& EncodedVoiceData, uint32 SampleRate, uint32 NumChannels, uint32 SizeBeforeEncode);
/** If we should play audio on same client */
virtual bool ShouldMuteLocal();
/** If we should play audio on other clients */
virtual bool ShouldMuteGlobal();
virtual void OnServerAudioReceived(uint8* VoiceData, uint32 VoiceDataSize, bool ContainsHeaderData = true, uint32 SampleRate = 21000, uint32 NumChannels = 1) {};
void PlayVoiceSynced(uint8* VoiceData, uint32 VoiceDataSize, bool ContainsHeaderData=true, uint32 SampleRate=21000, uint32 NumChannels=1);
void PlayVoiceData(uint8* VoiceData, uint32 VoiceDataSize, bool ContainsHeaderData=true, uint32 SampleRate=21000, uint32 NumChannels=1);
void PlayVoiceData(uint8* VoiceData, uint32 VoiceDataSize, bool ContainsHeaderData, FAnimationSequence FaceSequence, uint32 SampleRate = 21000, uint32 NumChannels = 1);
//UFUNCTION(BlueprintCallable, Category = "Convai")
void ForcePlayVoice(USoundWave* VoiceToPlay);
void StopVoice();
void PauseVoice();
void ResumeVoice();
void StopVoiceWithFade(float InRemainingVoiceFadeOutTime);
void ResetVoiceFade();
void UpdateVoiceFade(float DeltaTime);
bool IsVoiceCurrentlyFading();
void ClearAudioFinishedTimer();
bool IsLocal();
/** Called when starts to talk */
UPROPERTY(BlueprintAssignable, Category = "Convai")
FOnStartedTalkingSignature OnStartedTalking;
/** Called when stops to talk */
UPROPERTY(BlueprintAssignable, Category = "Convai")
FOnFinishedTalkingSignature OnFinishedTalking;
/** Called when there are LipSync visemes available */
UPROPERTY(BlueprintAssignable, Category = "Convai|LipSync")
FOVRLipSyncVisemesDataReadySignature OnVisemesReady;
IConvaiLipSyncInterface* FindFirstLipSyncComponent();
UFUNCTION(BlueprintCallable, Category = "Convai|LipSync")
bool SetLipSyncComponent(UActorComponent* LipSyncComponent);
/** Returns true, if an LipSync Component was available and attached to the character */
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|LipSync")
bool SupportsLipSync();
IConvaiVisionInterface* FindFirstVisionComponent();
UFUNCTION(BlueprintCallable, Category = "Convai|Vision")
bool SetVisionComponent(UActorComponent* VisionComponent);
/** Returns true, if an Vision Component was available and attached to the character */
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Vision")
bool SupportsVision();
bool ReplicateVoiceToNetwork;
public:
// UActorComponent interface
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// UObject Interface.
virtual void BeginDestroy() override;
public:
FTimerHandle AudioFinishedTimerHandle;
FTimerHandle LypSyncTimeoutTimerHandle;
double AudioEndTime = 0.0;
bool IsTalking = false;
float TotalVoiceFadeOutTime;
float RemainingVoiceFadeOutTime;
UPROPERTY()
USoundWaveProcedural* SoundWaveProcedural;
TArray<uint8> AudioDataBuffer;
TArray<uint8> ReceivedEncodedAudioDataBuffer;
IConvaiLipSyncInterface* ConvaiLipSync;
IConvaiVisionInterface* ConvaiVision;
void PlayLipSyncWithPrecomputedFacialAnimationSynced(FAnimationSequence& FaceSequence);
void PlayLipSyncWithPrecomputedFacialAnimation(FAnimationSequence FaceSequence);
void PlayLipSync(uint8* InPCMData, uint32 InPCMDataSize, uint32 InSampleRate, uint32 InNumChannels);
void StopLipSync();
void PauseLipSync();
void ResumeLipSync();
virtual bool CanUseLipSync();
virtual void ForceRecalculateLipsyncStartTime();
virtual bool CanUseVision();
void OnVisemesReadyCallback();
void OnLipSyncTimeOut();
UFUNCTION(BlueprintPure, Category = "Convai|LipSync", Meta = (Tooltip = "Returns last predicted viseme scores"))
const TArray<float> GetVisemes() const;
UFUNCTION(BlueprintPure, Category = "Convai|LipSync", Meta = (Tooltip = "Returns list of viseme names"))
const TArray<FString> GetVisemeNames() const;
UFUNCTION(BlueprintPure, Category = "Convai|LipSync", Meta = (Tooltip = "Returns map of blendshapes"))
const TMap<FName, float> ConvaiGetFaceBlendshapes() const;
UFUNCTION(BlueprintPure, Category = "Convai|LipSync", Meta = (Tooltip = "True if the output visemes is in Blendshape format"))
bool GeneratesVisemesAsBlendshapes();
void AddFaceDataToSend(FAnimationSequence FaceSequence);
// Should be called in the game thread
void AddPCMDataToSend(TArray<uint8> PCMDataToAdd, bool ContainsHeaderData = true, uint32 SampleRate = 21000, uint32 NumChannels = 1);
virtual void onAudioStarted();
virtual void onAudioFinished();
// Add to public section
enum class EAudioLipSyncState : uint8
{
Stopped UMETA(DisplayName = "Stopped"),
Playing UMETA(DisplayName = "Playing"),
WaitingOnLipSync UMETA(DisplayName = "Waiting On LipSync"),
WaitingOnAudio UMETA(DisplayName = "Waiting On Audio")
};
EAudioLipSyncState CurrentState;
// Simplified buffer structure
struct FAudioBuffer
{
TRingBuffer<uint8> Data;
float Duration;
uint32 SampleRate;
uint32 NumChannels;
FAudioBuffer() : Duration(0.0f), SampleRate(0), NumChannels(0) {}
void Reset()
{
Data.Empty();
Duration = 0.0f;
SampleRate = 0;
NumChannels = 0;
}
void Init(uint32 BufferSize)
{
Data.Init(BufferSize);
}
bool IsEmpty() const { return Data.RingDataUsage() == 0; }
float GetTotalDuration() const { return Duration; }
// Add data to the buffer
void AppendData(const uint8* NewData, uint32 DataSize)
{
// Validate input parameters to prevent crashes
if (!NewData || DataSize == 0)
{
return; // Ignore invalid data
}
Data.Enqueue(NewData, DataSize);
}
// Get data from the buffer (copies to the provided buffer)
uint32 GetData(uint8* OutBuffer, uint32 BufferSize) const
{
return Data.Peek(OutBuffer, BufferSize);
}
// Remove data from the buffer
void RemoveData(uint32 BytesToRemove)
{
// Ensure we don't try to remove more than what's available
uint32 BytesToActuallyRemove = FMath::Min(BytesToRemove, Data.RingDataUsage());
if (BytesToActuallyRemove > 0)
{
// The TRingBuffer::Dequeue method with nullptr is designed to discard data
// without copying it, which is exactly what we want for efficient removal
Data.Dequeue(nullptr, BytesToActuallyRemove);
}
}
};
struct FLipSyncBuffer
{
TArray<FAnimationSequence> Sequences;
float TotalDuration;
FLipSyncBuffer() : TotalDuration(0.0f) {}
void Reset()
{
Sequences.Empty();
TotalDuration = 0.0f;
}
bool IsEmpty() const { return Sequences.Num() == 0; }
float GetTotalDuration() const { return TotalDuration; }
void AddSequence(const FAnimationSequence& Sequence)
{
Sequences.Add(Sequence);
TotalDuration += Sequence.Duration;
}
};
FAudioBuffer AudioBuffer;
FLipSyncBuffer LipSyncBuffer;
// Configuration parameters
float MinBufferDuration;
float AudioLipSyncRatio;
float EnableSync;
// State management functions
void TransitionToState(EAudioLipSyncState NewState);
void HandleAudioReceived(uint8* AudioData, uint32 AudioDataSize, bool ContainsHeaderData, uint32 SampleRate, uint32 NumChannels);
void HandleLipSyncReceived(FAnimationSequence& FaceSequence);
bool TryPlayBufferedContent(bool force = false);
bool HasSufficientLipSync();
bool HasSufficientAudio() const;
void PlayBufferedContent(float Duration);
/**
* Returns the duration of content (audio and lipsync if applicable) that is
* currently playing or buffered and ready to play.
* @return Duration in seconds of content remaining
*/
UFUNCTION(BlueprintPure, Category = "Convai|Audio")
float GetRemainingContentDuration();
// Add to protected section
// Tracking variables for content
float TotalPlayingDuration; // Total duration of content currently being played
float TotalBufferedDuration; // Total duration of content buffered but not yet played
bool bIsSyncingAudioAndLipSync; // Whether we're syncing audio and lipsync
private:
// Critical section for protecting SoundWaveProcedural operations
FCriticalSection AudioConfigLock;
FThreadSafeBool IsAudioConfiguring;
// Buffer for pending audio data when lock is held
TArray<uint8> PendingAudioBuffer;
// Process any pending audio data
void ProcessPendingAudio();
bool InitEncoder(int32 InSampleRate, int32 InNumChannels, EAudioEncodeHint EncodeHint);
int32 Encode(const uint8* RawPCMData, uint32 RawDataSize, uint8* OutCompressedData, uint32& OutCompressedDataSize);
void DestroyOpusEncoder();
bool InitDecoder(int32 InSampleRate, int32 InNumChannels);
void Decode(const uint8* CompressedData, uint32 CompressedDataSize, uint8* OutRawPCMData, uint32& OutRawDataSize);
void DestroyOpusDecoder();
void DestroyOpus();
/** Sample rate encoding (supports 8000, 12000, 16000, 24000, 480000) */
int32 EncoderSampleRate;
/** Encoded channel count (supports 1,2) */
int32 EncoderNumChannels;
/**
* Number of samples encoded in a time slice "frame" (must match decoder)
* One frame defined as (2.5, 5, 10, 20, 40 or 60 ms) of audio data
* Voice encoding lower bound is 10ms (audio goes to 2.5ms).
* Voice encoding upper bound is 60ms (audio goes to 20ms).
* at 48 kHz the permitted values are 120 (2.5ms), 240 (5ms), 480 (10ms), 960 (20ms), 1920 (40ms), and 2880 (60ms)
*/
int32 EncoderFrameSize;
/** Opus encoder stateful data */
struct OpusEncoder* Encoder;
/** Last value set in the call to Encode() */
uint8 EncoderGeneration;
/** Sample rate to decode into, regardless of encoding (supports 8000, 12000, 16000, 24000, 480000) */
int32 DecoderSampleRate;
/** Decoded channel count (supports 1,2) */
int32 DecoderNumChannels;
/**
* Number of samples encoded in a time slice (must match encoder)
* at 48 kHz the permitted values are 120, 240, 480, 960, 1920, and 2880
*/
int32 DecoderFrameSize;
/** Opus decoder stateful data */
struct OpusDecoder* Decoder;
/** Generation value received from the last incoming packet */
uint8 DecoderLastGeneration;
// Pre-allocated temporary buffer for audio playback
TArray<uint8> TempAudioBuffer;
static constexpr uint32 TempBufferSize = 1024 * 1024 * 3; // 3 MB buffer
};

View File

@@ -0,0 +1,512 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "Http.h"
#include "ConvaiDefinitions.h"
#include "ConvaiChatBotProxy.generated.h"
//http log
DECLARE_LOG_CATEGORY_EXTERN(ConvaiBotHttpLog, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FBotQueryHttpResponseCallbackSignature, USoundWave*, AudioContent, FString, BotText, FString, NewSessionID, FString, ClassifiedAction);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FBotQueryHttpResponseWithUserTextCallbackSignature, USoundWave*, AudioContent, FString, BotText, FString, UserQuery, FString, NewSessionID, FString, ClassifiedAction);
class USoundWave;
class UTexture2D;
/**
*
*/
UCLASS()
class UConvaiChatBotQueryProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FBotQueryHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FBotQueryHttpResponseCallbackSignature OnFailure;
/**
* @param UserQuery The text input to the character
* @param VoiceResponse Whether or not we want a voice response
* @param CharID The character ID that we want to get response from
* @param SessionID The session ID used to track memory of a previous conversation
* @param Classification Whether or not use classification
* @param ClassLabels Array of classes to output a class from
*/
UFUNCTION(BlueprintCallable, meta = (/*DeprecatedFunction, DeprecationMessage = "Use Send Text in 'Convai Player' component for better latency",*/ BlueprintInternalUseOnly = "true", DisplayName = "DEPRECEATED Convai Chatbot Get Response", WorldContext = "WorldContextObject"), Category = "Convai|DEPRECATED")
static UConvaiChatBotQueryProxy* CreateChatBotQueryProxy(UObject* WorldContextObject,
FString UserQuery,
bool VoiceResponse,
FString CharID,
FString SessionID,
bool Classification,
TArray<FString> ClassLabels);
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
FString UserQuery;
bool VoiceResponse;
FString CharID;
FString SessionID;
bool Classification;
TArray<FString> ClassLabels;
// Outputs
USoundWave* AudioContent;
FString BotText;
FString NewSessionID;
FString ClassifiedAction;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
/**
*
*/
UCLASS()
class UConvaiChatBotQueryFromAudioProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FBotQueryHttpResponseWithUserTextCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FBotQueryHttpResponseWithUserTextCallbackSignature OnFailure;
/**
* Get a response from a character using a path to an audio file
* @param Filename The path to the audio file, it can be relative to the game or an absolute path
* @param VoiceResponse Whether or not we want a voice response
* @param CharID The character ID that we want to get response from
* @param SessionID The session ID used to track memory of a previous conversation
* @param Classification Whether or not use classification
* @param ClassLabels Array of classes to output a class from
*/
UFUNCTION(BlueprintCallable, meta = (/*DeprecatedFunction, DeprecationMessage = "Use Start Talking in 'Convai Player' component for better latency",*/ BlueprintInternalUseOnly = "true", DisplayName = "DEPRECEATED Convai Chatbot Get Response From Audio file", WorldContext = "WorldContextObject"), Category = "Convai|DEPRECATED")
static UConvaiChatBotQueryFromAudioProxy* CreateChatBotQueryFromAudioProxy(
UObject* WorldContextObject,
FString Filename,
bool VoiceResponse,
FString CharID,
FString SessionID,
bool Classification,
TArray<FString> ClassLabels);
/**
* Get a response from a character using a sound wave
* @param SoundWave The audio sound wave
* @param VoiceResponse Whether or not we want a voice response
* @param CharID The character ID that we want to get response from
* @param SessionID The session ID used to track memory of a previous conversation
* @param Classification Whether or not use classification
* @param ClassLabels Array of classes to output a class from
*/
UFUNCTION(BlueprintCallable, meta = (/*DeprecatedFunction, DeprecationMessage = "Use Start Talking in 'Convai Player' component for better latency",*/ BlueprintInternalUseOnly = "true", DisplayName = "DEPRECEATED Convai Chatbot Get Response From SoundWav", WorldContext = "WorldContextObject"), Category = "Convai|DEPRECATED")
static UConvaiChatBotQueryFromAudioProxy* CreateChatBotQueryFromSoundWavProxy(
UObject* WorldContextObject,
USoundWave* SoundWave,
bool VoiceResponse,
FString CharID,
FString SessionID,
bool Classification,
TArray<FString> ClassLabels);
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
TArray<uint8> Payload;
bool VoiceResponse;
FString CharID;
FString SessionID;
bool Classification;
TArray<FString> ClassLabels;
// Outputs
USoundWave* AudioContent;
FString BotText;
FString UserQuery;
FString NewSessionID;
FString ClassifiedAction;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBotCreateHttpResponseCallbackSignature, FString, CharID);
UCLASS()
class UConvaiChatBotCreateProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FBotCreateHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FBotCreateHttpResponseCallbackSignature OnFailure;
/**
* Creates a new character and outputs its character ID
* @param CharName The character name
* @param Voice The voice type to be used
* @param Backstory The backstory for the character
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Create Character", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotCreateProxy* CreateCharacterCreateProxy(UObject* WorldContextObject, FString CharName, FString Voice, FString Backstory);
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use \"Convai Create Character\" instead", BlueprintInternalUseOnly = "true", DisplayName = "Convai Get All Chatbots IDs", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotCreateProxy* CreateChatBotCreateProxy(UObject* WorldContextObject, FString CharName, FString Voice, FString Backstory)
{
return CreateCharacterCreateProxy(WorldContextObject, CharName, Voice, Backstory);
}
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
FString CharName;
FString Voice;
FString Backstory;
// Outputs
FString CharID;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBotUpdateHttpResponseCallbackSignature);
UCLASS()
class UConvaiChatBotUpdateProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FBotUpdateHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FBotUpdateHttpResponseCallbackSignature OnFailure;
/**
* Updates an already created character, IMPORTANT: if you do not want to update some property (e.g. Name) then leave it as empty string and it will not update
* @param CharID The character's ID that is going to be updated
* @param NewVoice The new voice type to be used, leave empty if you do not want to update
* @param NewBackstory The new backstory, leave empty if you do not want to update
* @param NewCharName The new character name, leave empty if you do not want to update
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Update Character", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotUpdateProxy* CreateCharacterUpdateProxy(UObject* WorldContextObject, FString CharID, FString NewVoice, FString NewBackstory, FString NewCharName, FString NewLanguage);
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use \"Convai Update Character\" instead", BlueprintInternalUseOnly = "true", DisplayName = "Convai Update Chatbot", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotUpdateProxy* CreateChatBotUpdateProxy(UObject* WorldContextObject, FString CharID, FString NewVoice, FString NewBackstory, FString NewCharName, FString NewLanguage)
{
return CreateCharacterUpdateProxy(WorldContextObject, CharID, NewVoice, NewBackstory, NewCharName, NewLanguage);
}
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
FString CharID;
FString NewCharName;
FString NewVoice;
FString NewBackstory;
FString NewLanguage;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FBotGetDetailsHttpResponseCallbackSignature
, FString, character_name
, FString, voice_type
, FString, backstory
, FString, LanguageCode
, bool, HasReadyPlayerMeLink
, FString, ReadyPlayerMeLink
, FString, AvatarImageLink);
UCLASS()
class UConvaiChatBotGetDetailsProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
public:
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FBotGetDetailsHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FBotGetDetailsHttpResponseCallbackSignature OnFailure;
/**
* Initiates a post request to the Character API.
* @param CharID The character's ID, you can find it from the Dashboard on the Convai.com website
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Get Character Details", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotGetDetailsProxy* CreateCharacterGetDetailsProxy(UObject* WorldContextObject, FString CharID);
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use \"Convai Get Character Details\" instead", BlueprintInternalUseOnly = "true", DisplayName = "Convai Get All Chatbots IDs", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotGetDetailsProxy* CreateChatBotGetDetailsProxy(UObject* WorldContextObject, FString CharID)
{
return CreateCharacterGetDetailsProxy(WorldContextObject, CharID);
}
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
// inputs
FString URL;
FString CharID;
// outputs
FString character_name;
//FString user_id;
FString voice_type;
//FString timestamp;
FString backstory;
FString LanguageCode;
bool HasReadyPlayerMeLink;
FString ReadyPlayerMeLink;
FString AvatarImageLink;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBotGetCharsHttpResponseCallbackSignature, const TArray<FString>&, CharIDs);
UCLASS()
class UConvaiChatBotGetCharsProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FBotGetCharsHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FBotGetCharsHttpResponseCallbackSignature OnFailure;
/**
* Gets all character IDs created by the user.
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Get All Characters IDs", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotGetCharsProxy* CreateCharacterGetCharsProxy(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use \"Convai Get All Characters IDs\" instead", BlueprintInternalUseOnly = "true", DisplayName = "Convai Get All Chatbots IDs", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiChatBotGetCharsProxy* CreateChatBotGetCharsProxy(UObject* WorldContextObject)
{
return CreateCharacterGetCharsProxy(WorldContextObject);
}
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
// Outputs
TArray<FString> CharIDs;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDownloadImageHttpResponseCallbackSignature, UTexture2D*, Image);
UCLASS()
class UConvaiDownloadImageProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FDownloadImageHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FDownloadImageHttpResponseCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Download Image", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiDownloadImageProxy* CreateDownloadImageProxy(UObject* WorldContextObject, FString URL);
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Download Image using RPM Link", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UConvaiDownloadImageProxy* CreateDownloadImageForRPMProxy(UObject* WorldContextObject, FString URL);
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
// Outputs
UTexture2D* Image;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};
USTRUCT(BlueprintType)
struct FAvailableVoices
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, category = "Convai|Language")
TMap<FString, FVoiceLanguageStruct> AvailableVoices;
FAvailableVoices()
{}
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGetAvailableVoicesCallbackSignature, const FAvailableVoices&, AvailableVoices);
UCLASS()
class UConvaiGetAvailableVoicesProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
public:
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FGetAvailableVoicesCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FGetAvailableVoicesCallbackSignature OnFailure;
/**
* Gets all available voices.
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Get Available Voices"), Category = "Convai|REST API")
static UConvaiGetAvailableVoicesProxy* CreateGetAvailableVoicesProxy(EVoiceType VoiceType, ELanguageType LanguageType, EGenderType Gender);
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
FString URL;
EVoiceType FilterVoiceType;
ELanguageType FilterLanguageType;
EGenderType FilterGender;
// Output
FAvailableVoices AvailableVoices;
private:
// Helper functions
bool ParseAllVoiceData(const FString& JsonString, TMap<FString, FVoiceLanguageStruct>& FilteredVoices);
bool ParseVoiceData(TSharedPtr<FJsonObject> JsonObject, FVoiceLanguageStruct& OutVoice);
FString GetLanguageCodeFromEnum(ELanguageType LanguageType);
FString GetGenderFromEnum(EGenderType GenderType);
FString GetVoiceTypeFromEnum(EVoiceType VoiceType);
};

View File

@@ -0,0 +1,466 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
//#include "CoreMinimal.h"
#include "Components/AudioComponent.h"
#include "ConvaiAudioStreamer.h"
#include "Containers/Map.h"
#include "ConvaiDefinitions.h"
#include "ConvaiChatbotComponent.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiChatbotComponentLog, Log, All);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_ThreeParams(FOnTranscriptionReceivedSignature_Deprecated, UConvaiChatbotComponent, OnTranscriptionReceivedEvent, FString, Transcription, bool, IsTranscriptionReady, bool, IsFinal);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams(FOnTranscriptionReceivedSignature_V2, UConvaiChatbotComponent, OnTranscriptionReceivedEvent_V2, UConvaiChatbotComponent*, ChatbotComponent, UConvaiPlayerComponent*, InteractingPlayerComponent, FString, PlayerName, FString, Transcription, bool, IsTranscriptionReady, bool, IsFinal);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams(FOnTextReceivedSignature_Deprecated, UConvaiChatbotComponent, OnTextReceivedEvent, FString, CharacterName, FString, BotText, float, AudioDuration, bool, IsFinal);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams(FOnTextReceivedSignature_V2, UConvaiChatbotComponent, OnTextReceivedEvent_V2, UConvaiChatbotComponent*, ChatbotComponent, UConvaiPlayerComponent*, InteractingPlayerComponent, FString, CharacterName, FString, BotText, float, AudioDuration, bool, IsFinal);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(FOnActionReceivedSignature_Deprecated, UConvaiChatbotComponent, OnActionReceivedEvent, const TArray<FConvaiResultAction>&, SequenceOfActions);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_ThreeParams(FOnActionReceivedSignature_V2, UConvaiChatbotComponent, OnActionReceivedEvent_V2, UConvaiChatbotComponent*, ChatbotComponent, UConvaiPlayerComponent*, InteractingPlayerComponent, const TArray<FConvaiResultAction>&, SequenceOfActions);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FOnEmotionReceivedSignature, UConvaiChatbotComponent, OnEmotionStateChangedEvent, UConvaiChatbotComponent*, ChatbotComponent, UConvaiPlayerComponent*, InteractingPlayerComponent);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(FOnCharacterDataLoadSignature_Deprecated, UConvaiChatbotComponent, OnCharacterDataLoadEvent, bool, Success);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FOnCharacterDataLoadSignature_V2, UConvaiChatbotComponent, OnCharacterDataLoadEvent_V2, UConvaiChatbotComponent*, ChatbotComponent, bool, Success);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FOnNarrativeSectionReceivedSignature, UConvaiChatbotComponent, OnNarrativeSectionReceivedEvent, UConvaiChatbotComponent*, ChatbotComponent, FString, NarrativeSectionID);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_ThreeParams(FOnInteractionIDReceivedSignature, UConvaiChatbotComponent, OnInteractionIDReceivedEvent, UConvaiChatbotComponent*, ChatbotComponent, UConvaiPlayerComponent*, InteractingPlayerComponent, FString, InteractionID);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(FOnFailureSignature, UConvaiChatbotComponent, OnFailureEvent);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FOnInterruptedSignature, UConvaiChatbotComponent, OnInterruptedEvent, UConvaiChatbotComponent*, ChatbotComponent, UConvaiPlayerComponent*, InteractingPlayerComponent);
// TODO (Mohamed): Manage onDestroy/onEndPlay - should end any on-going streams
class UConvaiPlayerComponent;
class USoundWaveProcedural;
class UConvaiGRPCGetResponseProxy;
class UConvaiChatBotGetDetailsProxy;
UCLASS(Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent), DisplayName = "Convai Chatbot")
class CONVAI_API UConvaiChatbotComponent : public UConvaiAudioStreamer
{
GENERATED_BODY()
public:
UConvaiChatbotComponent();
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/**
* Returns true, if the character is being talked to, is talking, or is processing the response.
*/
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai")
bool IsInConversation();
/**
* Returns true, if the character is still processing and has not received the full response yet.
*/
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai", meta = (DisplayName = "Is Thinking"))
bool IsProcessing();
/**
* Returns true, if the character is currently listening to a player.
*/
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai")
bool IsListening();
/**
* Returns true, if the character is currently talking.
*/
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai", meta = (DisplayName = "Is Talking"))
bool GetIsTalking();
/** Returns time elapsed since the character started talking */
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Voice")
float GetTalkingTimeElapsed();
/** Returns time remaining audio time for the character to speak */
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Voice")
float GetTalkingTimeRemaining();
UPROPERTY(EditAnywhere, Category = "Convai", Replicated, BlueprintSetter = LoadCharacter)
FString CharacterID;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated)
FString CharacterName;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated)
FString VoiceType;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated)
FString Backstory;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated)
FString LanguageCode;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated)
FString ReadyPlayerMeLink;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated)
FString AvatarImageLink;
UPROPERTY(BlueprintReadOnly, Category = "Convai", Replicated, meta = (DisplayName = "Interacting Player"))
UConvaiPlayerComponent* CurrentConvaiPlayerComponent;
UPROPERTY(BlueprintReadOnly, Category = "Convai|Actions", Replicated)
TArray<FConvaiResultAction> ActionsQueue;
UPROPERTY(Replicated)
FConvaiEmotionState EmotionState;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai|Emotion", Replicated)
bool LockEmotionState = false;
/**
* Used to track memory of a previous conversation, set to -1 means no previous conversation,
* this property will change as you talk to the character, you can save the session ID for a
* conversation and then set it back later on to resume a conversation
*/
UPROPERTY(BlueprintReadWrite, Category = "Convai", Replicated)
FString SessionID = "-1";
/**
* Contains all relevant objects and characters in the scene including the (Player), and also all the actions doable by the character
*/
UPROPERTY(BlueprintReadWrite, Category = "Convai", BlueprintSetter = LoadEnvironment)
UConvaiEnvironment* Environment;
/**
* Time in seconds, for the character's voice audio to gradually degrade until it is completely turned off when interrupted.
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai")
float InterruptVoiceFadeOutDuration;
/**
* Value between -1 and 1, a value greater than zero over extends the emotions strength and a value less than zero dimishes it.
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai")
float EmotionOffset = 0;
/**
* Contains key value pairs used for Narrative Design.
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai|NarrativeDesign")
TMap<FString, FString> NarrativeTemplateKeys;
/**
* Extra information that can be passed to the character, can contain any important data that the chracter needs to know about without the
* need of player interaction or narrative triggers, e.g. Inventory items, Player health, key information to solve a puzzle, time of day, etc...
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai")
FString DynamicEnvironmentInfo;
/**
* Speaker ID used for long term memory (LTM)
*/
UPROPERTY(BlueprintReadOnly, Category = "Convai")
FString SpeakerID;
/**
* Reset the conversation with the character and remove previous memory, this is the same as setting the session ID property to -1.
*/
UFUNCTION(BlueprintCallable, Category = "Convai")
void ResetConversation();
/**
* Loads a new character using its ID
*/
UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly, Category = "Convai")
void LoadCharacter(FString NewCharacterID);
UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly, Category = "Convai")
void LoadEnvironment(UConvaiEnvironment* NewConvaiEnvironment);
/**
* Appends a TArray of FConvaiResultAction items to the existing ActionsQueue.
* If ActionsQueue is not empty, it takes the first element, appends the new array to it,
* and then reassigns it back to ActionsQueue. If ActionsQueue is empty, it simply sets ActionsQueue to the new array.
*
* @param NewActions Array of FConvaiResultAction items to be appended.
*
* @category Convai
*/
void AppendActionsToQueue(TArray<FConvaiResultAction> NewActions);
/**
* Marks the current action as completed and handles post-execution logic.
*
* @param IsSuccessful A boolean flag indicating whether the executed action was successful or not. If true, the next action in the queue will be processed. If false, the current action will be retried.
* @param Delay A float value representing the time in seconds to wait before attempting either the next action or retrying the current action, depending on the value of IsSuccessful.
*
* @note This function should be invoked after each action execution to manage the action queue.
*
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Actions")
void HandleActionCompletion(bool IsSuccessful, float Delay);
/**
* Checks if the ActionsQueue managed by the Convai chatbot component is empty.
*
* @return Returns true if the ActionsQueue is empty; otherwise, returns false.
*
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Convai|Actions")
bool IsActionsQueueEmpty();
/**
* Clears the ActionsQueue managed by the Convai chatbot component.
*
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Actions")
void ClearActionQueue();
/**
* Fetches the first action from the ActionsQueue managed by the Convai chatbot component.
*
* @param ConvaiResultAction Reference to a struct that will be populated with the details of the first action in the queue.
*
* @return Returns true if there is at least one action in the ActionsQueue and the struct has been successfully populated; otherwise, returns false.
*
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Convai|Actions")
bool FetchFirstAction(FConvaiResultAction& ConvaiResultAction);
/**
* Removes the first action from the ActionsQueue managed by the Convai chatbot component.
*
* @return Returns true if an action was successfully removed; otherwise, returns false.
*
*/
bool DequeueAction();
/**
* Starts executing the first action in the ActionsQueue by calling TriggerNamedBlueprintAction.
*
* @return Returns true if the first action was successfully started; otherwise, returns false.
*
*/
UFUNCTION()
bool StartFirstAction();
/**
* Triggers a specified Blueprint event or function on the owning actor based on the given action name and parameters.
*
* @param ActionName The name of the Blueprint event or function to trigger. This event or function should exist in the Blueprint that owns this component.
* @param ConvaiActionStruct A struct containing additional data or parameters to pass to the Blueprint event or function.
*
* @note The function attempts to dynamically find and call a Blueprint event or function in the owning actor's class. If the Blueprint event or function does not exist or if the signature doesn't match, the function will log a warning.
*
*/
bool TriggerNamedBlueprintAction(const FString& ActionName, FConvaiResultAction ConvaiActionStruct);
bool TryCallFunction(UObject* Object, const FString& FunctionName, FConvaiResultAction& ConvaiResultAction) const;
UFUNCTION(BlueprintCallable, Category = "Convai|Emotion")
void ForceSetEmotion(EBasicEmotions BasicEmotion, EEmotionIntensity Intensity, bool ResetOtherEmotions = false);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Convai|Emotion")
float GetEmotionScore(EBasicEmotions Emotion);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Convai|Emotion")
TMap<FName, float> GetEmotionBlendshapes();
UFUNCTION(BlueprintCallable, Category = "Convai|Emotion")
void ResetEmotionState();
public:
// UFUNCTION(BlueprintCallable, Category = "Convai|Voice")
void StartRecordingVoice();
// UFUNCTION(BlueprintCallable, Category = "Convai|Voice")
USoundWave* FinishRecordingVoice();
// UFUNCTION(BlueprintCallable, Category = "Convai|Voice")
bool PlayRecordedVoice(USoundWave* RecordedVoice);
public:
/** Called when a new action is received from the API */
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "_DEPRECATED On Actions Received"))
FOnActionReceivedSignature_Deprecated OnActionReceivedEvent;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Actions Received"))
FOnActionReceivedSignature_V2 OnActionReceivedEvent_V2;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Emotion State Changed"))
FOnEmotionReceivedSignature OnEmotionStateChangedEvent;
/** Called when new text is received from the API, AudioDuration = 0 if no audio was received */
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "_DEPRECATED On Text Received"))
FOnTextReceivedSignature_Deprecated OnTextReceivedEvent;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Text Received"))
FOnTextReceivedSignature_V2 OnTextReceivedEvent_V2;
/** Called when new transcription is available */
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "_DEPRECATED On Transcription Received"))
FOnTranscriptionReceivedSignature_Deprecated OnTranscriptionReceivedEvent;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Transcription Received"))
FOnTranscriptionReceivedSignature_V2 OnTranscriptionReceivedEvent_V2;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "_DEPRECATED On Character Data Loaded"))
FOnCharacterDataLoadSignature_Deprecated OnCharacterDataLoadEvent;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Character Data Loaded"))
FOnCharacterDataLoadSignature_V2 OnCharacterDataLoadEvent_V2;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Narrative Section Received"))
FOnNarrativeSectionReceivedSignature OnNarrativeSectionReceivedEvent;
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Interaction ID Received"))
FOnInteractionIDReceivedSignature OnInteractionIDReceivedEvent;
/** Called when the character is interrupted */
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Interrupted"))
FOnInterruptedSignature OnInterruptedEvent;
/** Called when there is an error */
UPROPERTY(BlueprintAssignable, Category = "Convai", meta = (DisplayName = "On Failure"))
FOnFailureSignature OnFailureEvent;
public:
//UFUNCTION(BlueprintCallable, DisplayName = "Begin Transmission")
void StartGetResponseStream(UConvaiPlayerComponent* InConvaiPlayerComponent, FString InputText, UConvaiEnvironment* InEnvironment, bool InGenerateActions, bool InVoiceResponse, bool ReplicateVoiceToNetwork, bool UseOverrideAuthKey, FString OverrideAuthKey, FString OverrideAuthHeader, uint32 InToken, FString InSpeakerID);
void FinishGetResponseStream(UConvaiPlayerComponent* InConvaiPlayerComponent);
UFUNCTION(BlueprintCallable, Category = "Convai", meta = (DisplayName = "Invoke Speech"))
void ExecuteNarrativeTrigger(FString TriggerMessage, UConvaiEnvironment* InEnvironment, bool InGenerateActions, bool InVoiceResponse, bool InReplicateOnNetwork);
UFUNCTION(BlueprintCallable, Category = "Convai", meta = (DisplayName = "Invoke Narrative Design Trigger"))
void InvokeNarrativeDesignTrigger(FString TriggerName, UConvaiEnvironment* InEnvironment, bool InGenerateActions, bool InVoiceResponse, bool InReplicateOnNetwork);
void InvokeTrigger_Internal(FString TriggerName, FString TriggerMessage, UConvaiEnvironment* InEnvironment, bool InGenerateActions, bool InVoiceResponse, bool InReplicateOnNetwork);
// Interrupts the current speech with a provided fade-out duration.
// The fade-out duration is controlled by the parameter 'InVoiceFadeOutDuration'.
UFUNCTION(BlueprintCallable, Category = "Convai")
void InterruptSpeech(float InVoiceFadeOutDuration);
// Trys to clear the Interacting player variable if there were no conversation going
// If Force clear was true, it will interrupt the conversation and clear the interacting player
UFUNCTION(BlueprintCallable, Category = "Convai")
void TryClearInteractingPlayer(bool& Success, bool Interrupt);
// Broadcasts an interruption of the current speech across a network, with a provided fade-out duration.
// This function ensures that the interruption is communicated reliably to all connected clients.
// The fade-out duration is controlled by the parameter 'InVoiceFadeOutDuration'.
UFUNCTION(NetMulticast, Reliable, Category = "VoiceNetworking")
void Broadcast_InterruptSpeech(float InVoiceFadeOutDuration);
public:
// AActorComponent interface
virtual void BeginPlay() override;
//virtual void OnRegister() override;
//virtual void OnUnregister() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// End AActorComponent interface
//~ Begin UObject Interface.
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void BeginDestroy() override;
//~ End UObject Interface.
//~ Begin UConvaiAudioStreamer Interface.
virtual bool CanUseLipSync() override;
virtual bool CanUseVision() override;
//~ End UConvaiAudioStreamer Interface.
private:
UConvaiChatBotGetDetailsProxy* ConvaiGetDetails();
TScriptDelegate<FWeakObjectPtr> ConvaiChatBotGetDetailsDelegate;
UFUNCTION()
void OnConvaiGetDetailsCompleted(FString ReceivedCharacterName, FString ReceivedVoiceType, FString ReceivedBackstory, FString ReceivedLanguageCode, bool HasReadyPlayerMeLink, FString ReceivedReadyPlayerMeLink, FString ReceivedAvatarImageLink);
private:
// Used when both the voice component ring buffer is empty and we have already sent all the current audio data over the stream
void onMicrophoneDataReceived();
// Returns false if the player has decided to stop sending audio
bool CheckTokenValidity();
// Called when a player time outs while sending audio data (for example 2 secs have passed without us getting any audio from him)
void OnPlayerTimeOut();
void ClearTimeOutTimer();
bool HasOnGoingGetResponseStream();
bool CanWriteToGetResponseStream();
bool ConsumeMicStreamIntoBuffer();
private:
void Start_GRPC_Request(bool UseOverrideAuthKey, FString OverrideAuthKey, FString OverrideAuthHeader, FString TriggerName = "", FString TriggerMessage = "");
void Bind_GRPC_Request_Delegates();
void Unbind_GRPC_Request_Delegates();
void Cleanup(bool StreamConnectionFinished = false);
private:
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_OnTranscriptionReceived(const FString& Transcription, bool IsTranscriptionReady, bool IsFinal);
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_onResponseDataReceived(const FString& ReceivedText, bool IsFinal);
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_onSessionIDReceived(const FString& ReceivedSessionID);
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_onActionSequenceReceived(const TArray<FConvaiResultAction>& ReceivedSequenceOfActions);
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_OnNarrativeSectionReceived(const FString& BT_Code, const FString& BT_Constants, const FString& ReceivedNarrativeSectionID);
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_onInteractionIDReceived(const FString& ReceivedInteractionID);
UFUNCTION(NetMulticast, Reliable, Category = "Convai")
void Broadcast_onEmotionReceived(const FString& ReceivedEmotionResponse, bool MultipleEmotions);
void OnTranscriptionReceived(FString Transcription, bool IsTranscriptionReady, bool IsFinal);
void onResponseDataReceived(const FString ReceivedText, const TArray<uint8>& ReceivedAudio, uint32 SampleRate, bool IsFinal);
void OnFaceDataReceived(FAnimationSequence FaceDataAnimation);
void onSessionIDReceived(FString ReceivedSessionID);
void onInteractionIDReceived(FString ReceivedInteractionID);
void onActionSequenceReceived(const TArray<FConvaiResultAction>& ReceivedSequenceOfActions);
void onEmotionReceived(FString ReceivedEmotionResponse, FAnimationFrame EmotionBlendshapesFrame, bool MultipleEmotions);
void onFinishedReceivingData();
void OnNarrativeSectionReceived(FString BT_Code, FString BT_Constants, FString ReceivedNarrativeSectionID);
void onFailure();
private:
UPROPERTY(Replicated, ReplicatedUsing = OnRep_EnvironmentData)
FConvaiEnvironmentDetails ConvaiEnvironmentDetails;
UFUNCTION()
void OnRep_EnvironmentData();
void UpdateEnvironmentData();
private:
UPROPERTY()
UConvaiChatBotGetDetailsProxy* ConvaiChatBotGetDetailsProxy;
UPROPERTY()
UConvaiGRPCGetResponseProxy* ConvaiGRPCGetResponseProxy;
bool GenerateActions; // Should we generate actions
bool TextInput; // Whether to use text or audio as input to the API
bool VoiceResponse; // Require audio response from the API
FString UserText; // Input text to send to the API in case of TextInput is set to true
uint32 Token; // Used to check if the Convai player component is still streaming to us
bool StreamInProgress = false; // Are we receiving mic audio from player?
FTimerHandle TimeOutTimerHandle; // Timeout handler for player not sending audio data through mic
FString LastTranscription;
bool ReceivedFinalTranscription;
bool ReceivedFinalData; // Did the character end his response
FString LastPlayerName;
TArray<uint8> PlayerInpuAudioBuffer;
TMap<FName, float> EmotionBlendshapes;
float TotalReceivedAudioDuration = 0;
TArray<uint8> RecordedAudio;
uint32 RecordedAudioSampleRate;
bool IsRecordingAudio;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "LipSyncInterface.h"
#include "Components/SceneComponent.h"
#include "Containers/Map.h"
#include "ConvaiDefinitions.h"
#include "ConvaiFaceSync.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiFaceSyncLog, Log, All);
UCLASS(meta = (BlueprintSpawnableComponent), DisplayName = "Convai Face Sync")
class CONVAI_API UConvaiFaceSyncComponent : public USceneComponent, public IConvaiLipSyncInterface
{
GENERATED_BODY()
public:
UConvaiFaceSyncComponent();
virtual ~UConvaiFaceSyncComponent();
// UActorComponent interface
virtual void BeginPlay() override;
// virtual void OnRegister() override;
// virtual void OnUnregister() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
// End UActorComponent interface
// IConvaiLipSyncInterface
virtual void ConvaiInferFacialDataFromAudio(uint8* InPCMData, uint32 InPCMDataSize, uint32 InSampleRate, uint32 InNumChannels) override { return; }
virtual void ConvaiStopLipSync() override;
virtual TArray<float> ConvaiGetVisemes() override
{
TArray<float> VisemeValues;
CurrentBlendShapesMap.GenerateValueArray(VisemeValues);
return VisemeValues;
}
virtual TArray<FString> ConvaiGetVisemeNames() override { return ConvaiConstants::VisemeNames; }
// End IConvaiLipSyncInterface interface
// IConvaiLipSyncExtendedInterface
virtual void ConvaiApplyPrecomputedFacialAnimation(uint8* InPCMData, uint32 InPCMDataSize, uint32 InSampleRate, uint32 InNumChannels, FAnimationSequence FaceSequence) override;
virtual void ConvaiApplyFacialFrame(FAnimationFrame FaceFrame, float Duration) override;
virtual bool RequiresPrecomputedFaceData() override { return true; }
virtual bool GeneratesVisemesAsBlendshapes() override { return ToggleBlendshapeOrViseme; }
virtual TMap<FName, float> ConvaiGetFaceBlendshapes() override { return CurrentBlendShapesMap; }
// End IConvaiLipSyncExtendedInterface interface
virtual void Apply_StartEndFrames_PostProcessing(const int& CurrentFrameIndex, const int& NextFrameIndex, float& Alpha, TMap<FName, float>& StartFrame, TMap<FName, float>& EndFrame){}
virtual void ApplyPostProcessing() {}
// UFUNCTION(BlueprintCallable, Category = "Convai|LipSync")
void StartRecordingLipSync();
// UFUNCTION(BlueprintCallable, Category = "Convai|LipSync")
FAnimationSequenceBP FinishRecordingLipSync();
// UFUNCTION(BlueprintCallable, Category = "Convai|LipSync")
bool PlayRecordedLipSync(FAnimationSequenceBP RecordedLipSync, int StartFrame, int EndFrame, float OverwriteDuration);
bool IsValidSequence(const FAnimationSequence &Sequence);
bool IsPlaying();
// Record the current time if this is the first LipSync sequence to be received after silence
virtual void CalculateStartingTime();
virtual void ForceRecalculateStartTime() override;
void ClearMainSequence();
TMap<FName, float> InterpolateFrames(const TMap<FName, float>& StartFrame, const TMap<FName, float>& EndFrame, float Alpha);
TMap<FName, float> GenerateZeroFrame() { return GeneratesVisemesAsBlendshapes() ? ZeroBlendshapeFrame : ZeroVisemeFrame; }
void SetCurrentFrametoZero()
{
if (GeneratesVisemesAsBlendshapes())
CurrentBlendShapesMap = ZeroBlendshapeFrame;
else
CurrentBlendShapesMap = ZeroVisemeFrame;
}
TMap<FName, float> GetCurrentFrame() { return CurrentBlendShapesMap; }
const static TMap<FName, float> ZeroBlendshapeFrame;
const static TMap<FName, float> ZeroVisemeFrame;
//UPROPERTY(EditAnywhere, Category = "Convai|LipSync")
float AnchorValue = 0.5;
UPROPERTY(EditAnywhere, Category = "Convai|LipSync")
bool ToggleBlendshapeOrViseme = false;
protected:
float CurrentSequenceTimePassed;
TMap<FName, float> CurrentBlendShapesMap;
FAnimationSequence MainSequenceBuffer;
FAnimationSequence RecordedSequenceBuffer;
FCriticalSection SequenceCriticalSection;
FCriticalSection RecordingCriticalSection;
bool Stopping;
bool IsRecordingLipSync;
double StartTime;
bool bIsPlaying;
};

View File

@@ -0,0 +1,332 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "ConvaiSubsystem.h"
#include "CoreMinimal.h"
#include "Sound/SoundWave.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "HAL/ThreadSafeBool.h"
#include "ConvaiDefinitions.h"
#include "Containers/Map.h"
#include "ConvaiGRPC.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiGRPCLog, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(ConvaiGRPCFeedBackLog, Log, All);
class USoundWave;
class UConvaiEnvironment;
struct FAnimationFrame;
struct FAnimationSequence;
struct FConvaiResultAction;
DECLARE_DELEGATE_ThreeParams(FConvaiGRPCOnNarrativeDataSignature, const FString /*BT_Code*/, const FString /*BT_Constants*/, const FString /*NarrativeSectionID*/);
DECLARE_DELEGATE_ThreeParams(FConvaiGRPCOnTranscriptionSignature, const FString /*Transcription*/, bool /*IsTranscriptionReady*/, bool /*IsFinal*/);
DECLARE_DELEGATE_FourParams(FConvaiGRPCOnDataSignature, const FString /*ReceivedText*/, const TArray<uint8>& /*ReceivedAudio*/, uint32 /*SampleRate*/, bool /*IsFinal*/);
DECLARE_DELEGATE_OneParam(FConvaiGRPCOnFaceDataSignature, FAnimationSequence /*FaceData*/);
DECLARE_DELEGATE_OneParam(FConvaiGRPCOnActionsSignature, const TArray<FConvaiResultAction>& /*ActionSequence*/);
DECLARE_DELEGATE_ThreeParams(FConvaiGRPCOnEmotionSignature, FString /*EmotionResponse*/, FAnimationFrame /*EmotionBlendshapes*/, bool /*MultipleEmotions*/);
DECLARE_DELEGATE_OneParam(FConvaiGRPCOnSessiondIDSignature, FString /*SessionID*/);
DECLARE_DELEGATE_OneParam(FConvaiGRPCOnInteractionIDSignature, FString /*InteractionID*/);
DECLARE_DELEGATE(FConvaiGRPCOnEventSignature);
USTRUCT()
struct FConvaiGRPCVisionParams
{
GENERATED_BODY()
int width;
int height;
TArray<uint8> data;
FConvaiGRPCVisionParams()
: width(0)
, height(0)
, data(TArray<uint8>())
{
}
};
USTRUCT()
struct FConvaiGRPCGetResponseParams
{
GENERATED_BODY()
FString UserQuery;
FString TriggerName;
FString TriggerMessage;
FString CharID;
bool VoiceResponse;
bool RequireFaceData;
bool GeneratesVisemesAsBlendshapes;
FString SessionID;
UConvaiEnvironment* Environment;
bool GenerateActions;
FConvaiGRPCVisionParams ConvaiGRPCVisionParams;
FString AuthKey;
FString AuthHeader;
TMap<FString, FString> Narrative_Template_Keys;
FString DynamicEnvironmentInfo;
FString SpeakerID;
FConvaiGRPCGetResponseParams()
: UserQuery(TEXT(""))
, TriggerName(TEXT(""))
, TriggerMessage(TEXT(""))
, CharID(TEXT(""))
, VoiceResponse(false)
, RequireFaceData(false)
, GeneratesVisemesAsBlendshapes(false)
, SessionID(TEXT(""))
, Environment(nullptr)
, GenerateActions(false)
, AuthKey(TEXT(""))
, AuthHeader(TEXT(""))
, DynamicEnvironmentInfo(TEXT(""))
, SpeakerID(TEXT(""))
{
}
};
/**
*
*/
UCLASS()
class UConvaiGRPCGetResponseProxy : public UObject
{
GENERATED_BODY()
public:
// Called when user transcription is received
FThreadSafeDelegateWrapper<FConvaiGRPCOnTranscriptionSignature> OnTranscriptionReceived;
// Called when new text and/or Audio data is received
FThreadSafeDelegateWrapper<FConvaiGRPCOnDataSignature> OnDataReceived;
// Called when new face data is received
FThreadSafeDelegateWrapper<FConvaiGRPCOnFaceDataSignature> OnFaceDataReceived;
// Called when actions are received
FThreadSafeDelegateWrapper<FConvaiGRPCOnActionsSignature> OnActionsReceived;
// Called when new SessionID is received
FThreadSafeDelegateWrapper<FConvaiGRPCOnSessiondIDSignature> OnSessionIDReceived;
// Called when the InteractionID is received
FThreadSafeDelegateWrapper<FConvaiGRPCOnInteractionIDSignature> OnInteractionIDReceived;
FThreadSafeDelegateWrapper<FConvaiGRPCOnNarrativeDataSignature> OnNarrativeDataReceived;
// Called when new Emotion is received
FThreadSafeDelegateWrapper<FConvaiGRPCOnEmotionSignature> OnEmotionReceived;
// Called when the response stream is done
FThreadSafeDelegateWrapper<FConvaiGRPCOnEventSignature> OnFinish;
// Called when there is an unsuccessful response
FThreadSafeDelegateWrapper<FConvaiGRPCOnEventSignature> OnFailure;
//UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai GRPC Test", WorldContext = "WorldContextObject"), Category = "Convai|gRPC")
static UConvaiGRPCGetResponseProxy* CreateConvaiGRPCGetResponseProxy(UObject* WorldContextObject, FConvaiGRPCGetResponseParams ConvaiGRPCGetResponseParams);
void Activate();
void WriteAudioDataToSend(uint8* Buffer, uint32 Length);
void FinishWriting();
//~ Begin UObject Interface.
virtual void BeginDestroy() override;
//~ End UObject Interface.
bool IsStreamFinished()
{
return ReceivedFinish;
}
bool CanWriteToStream()
{
return !IsStreamFinished() && !LastWriteReceived && !FinishedWritingToStream;
}
private:
void CallFinish();
TArray<uint8> ConsumeFromAudioBuffer(bool& IsThisTheFinalWrite);
void LogAndEcecuteFailure(FString FuncName);
void ExtendDeadline();
void OnStreamInit(bool ok);
void OnStreamRead(bool ok);
void OnStreamWrite(bool ok);
void OnStreamWriteDone(bool ok);
void OnStreamFinish(bool ok);
FgRPC_Delegate OnInitStreamDelegate;
FgRPC_Delegate OnStreamReadDelegate;
FgRPC_Delegate OnStreamWriteDelegate;
FgRPC_Delegate OnStreamWriteDoneDelegate; // called when a stream->writesdone is successful
// called when a stream->finish is successful it means that the server decided
// to end the stream, we should check the status variable in that call
// https://groups.google.com/g/grpc-io/c/R0NTqKaHLdE
FgRPC_Delegate OnStreamFinishDelegate;
service::GetResponseRequest request;
std::unique_ptr<service::GetResponseResponse> reply;
// Context for the client. It could be used to convey extra information to
// the server and/or tweak certain RPC behaviors.
//std::unique_ptr<ClientContext> context;
// Storage for the status of the RPC upon completion.
grpc::Status status;
std::unique_ptr<::grpc::ClientAsyncReaderWriter< service::GetResponseRequest, service::GetResponseResponse>> stream_handler;
grpc::ClientContext client_context;
// True if we are writing audio to the server, false if we are in the receiving stage
bool StreamInProgress = false;
bool FailAlreadyExecuted = false;
bool FinishedWritingToStream = false;
std::unique_ptr<service::ConvaiService::Stub> stub_;
grpc::CompletionQueue* cq_;
uint32 NumberOfAudioBytesSent = 0;
private:
// Inputs
FConvaiGRPCGetResponseParams ConvaiGRPCGetResponseParams;
private:
// becomes true if we receive a non-ok header
FThreadSafeBool InformOnDataReceived;
// Stores the audio data to be streamed to the API
TArray<uint8> AudioBuffer;
// True when we are informed that the "AudioBuffer" is complete and no more audio will be received
FThreadSafeBool LastWriteReceived;
// Used to regulate access to the AudioBuffer to prevent racing condition
FCriticalSection m_mutex;
// Used to prevent early execution of the Init delegate
FCriticalSection GPRCInitSection;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
FThreadSafeBool ReceivedFinish;
FThreadSafeBool CalledFinish;
FThreadSafeBool ReceivedFinalResponse;
// Retry reading counters
int RetryCount = 0;
int MaxRetries = 3;
int TotalLipSyncResponsesReceived = 0;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSubmitFeedbackCallbackSignature, FString, FeedbackResponse);
UCLASS()
class UConvaiGRPCSubmitFeedbackProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
public:
// Called when there is a successful response
UPROPERTY(BlueprintAssignable)
FSubmitFeedbackCallbackSignature OnSuccess;
// Called when there is an unsuccessful response
UPROPERTY(BlueprintAssignable)
FSubmitFeedbackCallbackSignature OnFailure;
/**
* Initiates a feedback request to the gRPC API.
* @param InteractionID Identifies the message the feedback is being submitted for - Check "On Interaction ID Received" on the Convai Chatbot Component
* @param Thumbs Up False if the message is incorrect or inaccurate, True otherwise.
* @param FeedbackText Additionaly feedback that can further help the model learn why the user have given a thumbs up or thumbs down.
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Submit Feedback", WorldContext = "WorldContextObject"), Category = "Convai")
static UConvaiGRPCSubmitFeedbackProxy* CreateConvaiGRPCSubmitFeedbackProxy(UObject* WorldContextObject, FString InteractionID, bool ThumbsUp, FString FeedbackText);
virtual void Activate() override;
void failed();
void success();
void finish();
private:
FString InteractionID;
bool ThumbsUp;
FString FeedbackText;
FString Response;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
//~ Begin UObject Interface.
virtual void BeginDestroy() override;
//~ End UObject Interface.
private:
void LogAndEcecuteFailure(FString FuncName);
void OnStreamFinish(bool ok);
// called when a stream->finish is successful it means that the server decided
// to end the stream, we should check the status variable in that call
// https://groups.google.com/g/grpc-io/c/R0NTqKaHLdE
FgRPC_Delegate OnStreamFinishDelegate;
service::FeedbackRequest request;
std::unique_ptr<service::FeedbackResponse> reply;
// Storage for the status of the RPC upon completion.
grpc::Status status;
std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::service::FeedbackResponse>> stream_handler;
// Context for the client. It could be used to convey extra information to
// the server and/or tweak certain RPC behaviors.
grpc::ClientContext client_context;
std::unique_ptr<service::ConvaiService::Stub> stub_;
grpc::CompletionQueue* cq_;
};

View File

@@ -0,0 +1,77 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "ConvaiDefinitions.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "JsonUtilities.h"
#include "ConvaiNarrativeDesignProxy.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiNarrativeHTTP, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFetchNarrativeSectionsResponseMulticastCallbackSignature, const TArray<FNarrativeSection>&, NarrativeSections);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFetchNarrativeTriggersResponseMulticastCallbackSignature, const TArray<FNarrativeTrigger>&, NarrativeTriggers);
UCLASS()
class CONVAI_API UFetchNarrativeSectionsProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FFetchNarrativeSectionsResponseMulticastCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FFetchNarrativeSectionsResponseMulticastCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Fetch Narrative Sections", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UFetchNarrativeSectionsProxy* FetchNarrativeSections(UObject* WorldContextObject, FString CharacterId);
virtual void Activate() override;
TArray<FNarrativeSection> NarrativeSections;
private:
void OnHttpRequestCompleted(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
FString CharacterId;
TWeakObjectPtr<UWorld> WorldPtr;
void failed();
void success();
void finish();
};
UCLASS()
class CONVAI_API UFetchNarrativeTriggersProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FFetchNarrativeTriggersResponseMulticastCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FFetchNarrativeTriggersResponseMulticastCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Fetch Narrative Triggers", WorldContext = "WorldContextObject"), Category = "Convai|REST API")
static UFetchNarrativeTriggersProxy* FetchNarrativeTriggers(UObject* WorldContextObject, FString CharacterId);
virtual void Activate() override;
TArray<FNarrativeTrigger> NarrativeTriggers;
private:
void OnHttpRequestCompleted(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
FString CharacterId;
TWeakObjectPtr<UWorld> WorldPtr;
void failed();
void success();
void finish();
};

View File

@@ -0,0 +1,323 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/AudioComponent.h"
#include "RingBuffer.h"
#include "ConvaiAudioStreamer.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "DSP/BufferVectorOperations.h"
#include "ConvaiPlayerComponent.generated.h"
#define TIME_BETWEEN_VOICE_UPDATES_SECS 0.01
DECLARE_LOG_CATEGORY_EXTERN(ConvaiPlayerLog, Log, All);
DECLARE_DELEGATE(FonDataReceived_Delegate);
// class IVoiceCapture;
class UConvaiAudioCaptureComponent;
// UENUM(BlueprintType)
// enum class EHardwareInputFeatureBP : uint8
// {
// EchoCancellation,
// NoiseSuppression,
// AutomaticGainControl
// };
// TODO (Mohamed): Ensure both Chatbot and Player components have the same ReplicateVoiceToNetwork value
// TODO (Mohamed): Send Text should also be handled in this class (UConvaiPlayerComponent) like we did with voice
USTRUCT(BlueprintType)
struct FCaptureDeviceInfoBP
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Convai|Microphone")
FString DeviceName = "";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Convai|Microphone")
int DeviceIndex = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Convai|Microphone")
FString LongDeviceId = "";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Convai|Microphone")
int InputChannels = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Convai|Microphone")
int PreferredSampleRate = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Convai|Microphone")
bool bSupportsHardwareAEC = 0;
};
UCLASS(Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent), DisplayName = "Convai Player")
class CONVAI_API UConvaiPlayerComponent : public UConvaiAudioStreamer
{
GENERATED_BODY()
UConvaiPlayerComponent();
virtual void OnComponentCreated() override;
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
bool Init();
//~ Begin ActorComponent Interface.
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void BeginDestroy() override;
//~ End ActorComponent Interface.
public:
UPROPERTY(EditAnywhere, Category = "Convai", Replicated, BlueprintSetter = SetPlayerName)
FString PlayerName;
/**
* Sets a new player name
*/
UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly, Category = "Convai")
void SetPlayerName(FString NewPlayerName);
UFUNCTION(Server, Reliable, Category = "Convai|Network")
void SetPlayerNameServer(const FString& NewPlayerName);
/**
* Speaker ID used for long term memory (LTM)
*/
UPROPERTY(EditAnywhere, Category = "Convai", Replicated, BlueprintSetter = SetSpeakerID)
FString SpeakerID;
/**
* Sets a new Speaker ID
*/
UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly, Category = "Convai")
void SetSpeakerID(FString NewSpeakerID);
UFUNCTION(Server, Reliable, Category = "Convai|Network")
void SetSpeakerIDServer(const FString& NewSpeakerID);
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
bool GetDefaultCaptureDeviceInfo(FCaptureDeviceInfoBP& OutInfo);
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
bool GetCaptureDeviceInfo(FCaptureDeviceInfoBP& OutInfo, int DeviceIndex);
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
TArray<FCaptureDeviceInfoBP> GetAvailableCaptureDeviceDetails();
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
TArray<FString> GetAvailableCaptureDeviceNames();
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
void GetActiveCaptureDevice(FCaptureDeviceInfoBP& OutInfo);
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
bool SetCaptureDeviceByIndex(int DeviceIndex);
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
bool SetCaptureDeviceByName(FString DeviceName);
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
void SetMicrophoneVolumeMultiplier(float InVolumeMultiplier, bool& Success);
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Microphone")
void GetMicrophoneVolumeMultiplier(float& OutVolumeMultiplier, bool& Success);
// UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
// void GetIfHardwareFeatureIsSupported(EHardwareInputFeatureBP FeatureType, bool& Success);
// UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
// void SetHardwareFeatureEnabled(EHardwareInputFeatureBP FeatureType, bool bIsEnabled, bool& Success);
/**
* Start recording audio from the microphone, use "Finish Recording" function afterwards
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
void StartRecording();
/**
* Stops recording from the microphone and outputs the recorded audio as a Sound Wave
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
USoundWave* FinishRecording();
/**
* Starts streaming microphone audio to the character. Use "Finish Talking" afterward to let the character know that you are done talking
* @param ConvaiChatbotComponent The character to talk to
* @param Environment Holds all relevant objects and characters in the scene including the (Player), and also all the actions doable by the character. Use "CreateConvaiEnvironment" function to create and then use functions like (AddAction, AddCharacter, AddMainCharacter) to fill it up
* @param GenerateActions Whether or not to generate actions (Environment has to be given and valid)
* @param VoiceResponse If true it will generate a voice response, otherwise, it will only generate a text response.
* @param RunOnServer If true it will run this function on the server, this can be used in multiplayer sessions to allow other players to hear the character's voice response.
* @param StreamPlayerMic If true it will stream the player's voice to other players in the multiplayer session, this is the same effect as voice chat.
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
void StartTalking(
UConvaiChatbotComponent* ConvaiChatbotComponent,
UConvaiEnvironment* Environment,
bool GenerateActions,
bool VoiceResponse,
bool RunOnServer,
bool StreamPlayerMic,
bool UseServerAPI_Key);
/**
* Stops streaming microphone audio to the character.
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
void FinishTalking();
UFUNCTION(Server, Reliable, Category = "Convai|Network")
void StartTalkingServer(
class UConvaiChatbotComponent* ConvaiChatbotComponent,
bool EnvironemntSent,
const TArray<FString>& Actions,
const TArray<FConvaiObjectEntry>& Objects,
const TArray<FConvaiObjectEntry>& Characters,
FConvaiObjectEntry MainCharacter,
bool GenerateActions,
bool VoiceResponse,
bool StreamPlayerMic,
bool UseServerAPI_Key,
const FString& ClientAuthKey,
const FString& AuthHeader);
UFUNCTION(Server, Reliable, Category = "Convai|Network")
void FinishTalkingServer();
/**
* Sends text to the character.
* @param ConvaiChatbotComponent The character to talk to
* @param Text Text to be sent to the character.
* @param Environment Holds all relevant objects and characters in the scene including the (Player), and also all the actions doable by the character. Use "CreateConvaiEnvironment" function to create and then use functions like (AddAction, AddCharacter, AddMainCharacter) to fill it up
* @param GenerateActions Whether or not to generate actions (Environment has to be given and valid)
* @param VoiceResponse If true it will generate a voice response, otherwise, it will only generate a text response.
* @param RunOnServer If true it will run this function on the server, this can be used in multiplayer sessions to allow other players to hear the character's voice response.
*/
UFUNCTION(BlueprintCallable, Category = "Convai|Microphone")
void SendText(
UConvaiChatbotComponent* ConvaiChatbotComponent,
FString Text,
UConvaiEnvironment* Environment,
bool GenerateActions,
bool VoiceResponse,
bool RunOnServer,
bool UseServerAPI_Key);
UFUNCTION(Server, Reliable, Category = "Convai|Network")
void SendTextServer(
UConvaiChatbotComponent* ConvaiChatbotComponent,
const FString& Text,
bool EnvironemntSent,
const TArray<FString>& Actions,
const TArray<FConvaiObjectEntry>& Objects,
const TArray<FConvaiObjectEntry>& Characters,
FConvaiObjectEntry MainCharacter,
bool GenerateActions,
bool VoiceResponse,
bool UseServerAPI_Key,
const FString& ClientAuthKey,
const FString& AuthHeader);
virtual bool ShouldMuteLocal() override;
virtual bool ShouldMuteGlobal() override;
virtual void OnServerAudioReceived(uint8* VoiceData, uint32 VoiceDataSize, bool ContainsHeaderData = true, uint32 SampleRate = 21000, uint32 NumChannels = 1) override;
// UActorComponent interface
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
bool ConsumeStreamingBuffer(TArray<uint8>& Buffer);
UFUNCTION(Server, Reliable, Category = "Convai|Network")
void SetIsStreamingServer(bool value);
// Returns true if microphone audio is being streamed, false otherwise.
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Microphone", meta = (DisplayName = "Is Talking"))
bool GetIsStreaming()
{
return(IsStreaming);
}
// Returns true if microphone audio is being recorded, false otherwise.
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Microphone", meta = (DisplayName = "Is Recording"))
bool GetIsRecording()
{
return(IsRecording);
}
FonDataReceived_Delegate& getOnDataReceivedDelegate()
{
return onDataReceived_Delegate;
}
bool CheckTokenValidty(uint32 TokenToCheck)
{
return Token == TokenToCheck;
}
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai|PixelStreaming")
TWeakObjectPtr<USynthComponent> PixelStreamingAudioComponent;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Convai|PixelStreaming")
bool UsePixelStreamingMicInput;
bool IsPixelStreamingEnabledAndAllowed();
private:
uint32 GenerateNewToken()
{
Token += 1;
return Token;
}
UPROPERTY()
USoundWaveProcedural* VoiceCaptureSoundWaveProcedural;
//TSharedPtr<IVoiceCapture> VoiceCapture;
// Buffer used with recording
TArray<uint8> VoiceCaptureBuffer;
// Buffer used with streaming
// TODO (Mohamed): use TCircularAudioBuffer instead
TRingBuffer<uint8> VoiceCaptureRingBuffer;
UPROPERTY()
UConvaiAudioCaptureComponent* AudioCaptureComponent = nullptr;
UPROPERTY()
UConvaiChatbotComponent* CurrentConvaiChatbotComponent = nullptr;
void UpdateVoiceCapture(float DeltaTime);
void StartVoiceChunkCapture(float ExpectedRecordingTime = 0.01);
void StopVoiceChunkCapture();
void ReadRecordedBuffer(Audio::AlignedFloatBuffer& RecordedBuffer, float& OutNumChannels, float& OutSampleRate);
void StartAudioCaptureComponent();
void StopAudioCaptureComponent();
FonDataReceived_Delegate onDataReceived_Delegate;
bool IsRecording = false;
bool IsStreaming = false;
bool IsInit = false;
bool bShouldMuteGlobal;
float RemainingTimeUntilNextUpdate = 0;
// Used by a consumer (e.g. chatbot component) to validate if this player component is still streaming audio data to it
uint32 Token;
USoundSubmixBase* _FoundSubmix;
};

View File

@@ -0,0 +1,88 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "Http.h"
#include "ConvaiSpeechToTextProxy.generated.h"
//http log
DECLARE_LOG_CATEGORY_EXTERN(ConvaiS2THttpLog, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FS2THttpResponseCallbackSignature, FString, Response);
class USoundWave;
/**
*
*/
UCLASS()
class UConvaiSpeechToTextProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FS2THttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FS2THttpResponseCallbackSignature OnFailure;
/**
* Initiates a post request to the Speech To Text API.
* @param URL The url of the API endpoint.
* @param API_key The API key issued from the website
* @param SoundWave SoundWave that is to be transfered to text
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Speech To Text From SoundWave", WorldContext = "WorldContextObject"), Category = "Convai|Http")
static UConvaiSpeechToTextProxy* CreateSpeech2TextFromSoundWaveQueryProxy(UObject* WorldContextObject, USoundWave* SoundWave);
/**
* Initiates a post request to the Speech To Text API.
* @param URL The url of the API endpoint.
* @param API_key The API key issued from the website
* @param Payload Byte array containing the wav file
* @param bStereo Set to true if the wav file has 2 channels
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Speech To Text From byte array", WorldContext = "WorldContextObject"), Category = "Convai|Http")
static UConvaiSpeechToTextProxy* CreateSpeech2TextFromArrayQueryProxy(UObject* WorldContextObject, TArray<uint8> Payload);
/**
* Initiates a post request to the Speech To Text API.
* @param URL The url of the API endpoint.
* @param API_key The API key issued from the website
* @param filename The name of the wav file to be sent
* @param bStereo Set to true if the wav file has 2 channels
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Speech To Text From file", WorldContext = "WorldContextObject"), Category = "Convai|Http")
static UConvaiSpeechToTextProxy* CreateSpeech2TextFromFileNameQueryProxy(UObject* WorldContextObject, FString filename);
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
FString filename;
TArray<uint8> Payload;
bool bStereo;
FString Response;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};

View File

@@ -0,0 +1,163 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "Subsystems/SubsystemCollection.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "HAL/Runnable.h"
#include "HAL/ThreadSafeBool.h"
THIRD_PARTY_INCLUDES_START
#include "Proto/service.grpc.pb.h"
#undef min
#undef max
#undef UpdateResource
#undef GetObject
#undef GetUObject
#undef GetClassName
#undef InterlockedExchange
#undef InterlockedAnd
#undef InterlockedIncrement
#undef InterlockedDecrement
#undef InterlockedCompareExchange
#undef FastFence
#undef MemoryBarrier
#undef Yield
#undef CaptureStackBackTrace
#undef GetCurrentTime
THIRD_PARTY_INCLUDES_END
#include "ConvaiSubsystem.generated.h"
#ifdef __APPLE__
extern bool GetAppleMicPermission();
#endif
namespace grpc
{
class Channel;
template <class R>
class ClientAsyncResponseReader;
class ClientContext;
class CompletionQueue;
class Status;
template <class W, class R>
class ClientAsyncReaderWriter;
class ChannelCredentials;
}
//namespace service
//{
// //class ConvaiService;
//
// class ConvaiService::Stub;
// class GetResponseResponse;
// class GetResponseRequest;
//};
DECLARE_LOG_CATEGORY_EXTERN(ConvaiSubsystemLog, Log, All);
DECLARE_DELEGATE_OneParam(FgRPC_Delegate, bool);
class FgRPCClient : public FRunnable {
public:
FgRPCClient(std::string target, const std::shared_ptr<grpc::ChannelCredentials>& creds);
std::unique_ptr<service::ConvaiService::Stub> GetNewStub();
grpc::CompletionQueue* GetCompletionQueue();
public:
/**
* FRunnable Interface
* Loop while listening for completed responses.
*/
virtual uint32 Run() override;
void StartStub();
void CreateChannel();
void OnStateChange(bool ok);
virtual void Exit() override;
private:
mutable FCriticalSection CriticalSection;
FThreadSafeBool bIsRunning;
TUniquePtr<FRunnableThread> Thread;
private:
// Out of the passed in Channel comes the stub, stored here, our view of the
// server's exposed services.
std::unique_ptr<service::ConvaiService::Stub> stub_;
std::shared_ptr<grpc::Channel> Channel;
std::shared_ptr<grpc::ChannelCredentials> Creds;
std::string Target;
FgRPC_Delegate OnStateChangeDelegate;
// The producer-consumer queue we use to communicate asynchronously with the
// gRPC runtime.
grpc::CompletionQueue cq_;
};
UCLASS(meta = (DisplayName = "Convai Subsystem"))
class UConvaiSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UConvaiSubsystem();
// Begin USubsystem
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// End USubsystem
void GetAndroidMicPermission();
public:
TSharedPtr<FgRPCClient> gRPC_Runnable;
public:
// Register a chatbot component with the subsystem
void RegisterChatbotComponent(class UConvaiChatbotComponent* ChatbotComponent);
// Unregister a chatbot component from the subsystem
void UnregisterChatbotComponent(class UConvaiChatbotComponent* ChatbotComponent);
// Get all registered chatbot components
TArray<class UConvaiChatbotComponent*> GetAllChatbotComponents() const;
public:
// Register a player component with the subsystem
void RegisterPlayerComponent(class UConvaiPlayerComponent* PlayerComponent);
// Unregister a player component from the subsystem
void UnregisterPlayerComponent(class UConvaiPlayerComponent* PlayerComponent);
// Get all registered player components
TArray<class UConvaiPlayerComponent*> GetAllPlayerComponents() const;
private:
// Array to store all active chatbot components
UPROPERTY()
TArray<class UConvaiChatbotComponent*> RegisteredChatbotComponents;
private:
// Array to store all active player components
UPROPERTY()
TArray<class UConvaiPlayerComponent*> RegisteredPlayerComponents;
};

View File

@@ -0,0 +1,83 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "Http.h"
#include "ConvaiDefinitions.h"
#include "ConvaiTextToSpeechProxy.generated.h"
class USoundWave;
//http log
DECLARE_LOG_CATEGORY_EXTERN(ConvaiT2SHttpLog, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FT2SHttpResponseCallbackSignature, USoundWave*, Wave);
/**
*
*/
UCLASS()
class UConvaiTextToSpeechProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
// Called when there is a successful http response
UPROPERTY(BlueprintAssignable)
FT2SHttpResponseCallbackSignature OnSuccess;
// Called when there is an unsuccessful http response
UPROPERTY(BlueprintAssignable)
FT2SHttpResponseCallbackSignature OnFailure;
/**
* Initiates a post request to the Speech To Text API.
* @param API_key The API key issued from the website
* @param Transcript The text to be transformed to voice
* @param Voice The voice type to be used
*/
//UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Text To Speech", WorldContext = "WorldContextObject"), Category = "Convai|Http")
//static UConvaiTextToSpeechProxy* CreateTextToSpeechQueryProxy(UObject* WorldContextObject, FString Transcript, ETTS_Voice_Type Voice);
/**
* Initiates a post request to the Speech To Text API.
* @param API_key The API key issued from the website
* @param Transcript The text to be transformed to voice
* @param Voice The voice type to be used
*/
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Text To Speech", WorldContext = "WorldContextObject"), Category = "Convai|Http")
static UConvaiTextToSpeechProxy* CreateTextToSpeechQueryProxy(UObject* WorldContextObject, FString Transcript, FString Voice);
virtual void Activate() override;
void onHttpRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bWasSuccessful);
void failed();
void success();
void finish();
FString URL;
FString Transcript;
ETTS_Voice_Type Voice;
FString VoiceStr;
USoundWave* SoundWave;
// Pointer to the world
TWeakObjectPtr<UWorld> WorldPtr;
};

View File

@@ -0,0 +1,274 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ConvaiDefinitions.h"
#include "Utility/Log/ConvaiLogger.h"
#include "ConvaiUtils.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiUtilsLog, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(ConvaiFormValidationLog, Log, All);
class USoundWave;
class APlayerController;
class UObject;
class UConvaiSubsystem;
struct FAnimationFrame;
UCLASS()
class CONVAI_API UConvaiUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static UConvaiSubsystem* GetConvaiSubsystem(const UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static void StereoToMono(TArray<uint8> stereoWavBytes, TArray<uint8>& monoWavBytes);
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static bool ReadFileAsByteArray(const FString FilePath, TArray<uint8>& Bytes);
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static bool SaveByteArrayAsFile(FString FilePath, TArray<uint8> Bytes);
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static FString ByteArrayToString(TArray<uint8> Bytes);
// Writes a string to a file
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static bool WriteStringToFile(const FString& StringToWrite, const FString& FilePath);
// Reads a string from a file
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static bool ReadStringFromFile(FString& OutString, const FString& FilePath);
static float CalculateAudioDuration(uint32 AudioSize, uint8 Channels, uint32 SampleRate, uint8 SampleSize = 2);
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Utilities", meta = (WorldContext = "WorldContextObject", AutoCreateRefTerm = "IncludedCharacters, ExcludedCharacters"))
static void ConvaiGetLookedAtCharacter(UObject* WorldContextObject, APlayerController* PlayerController, float Radius, bool PlaneView, TArray<UObject*> IncludedCharacters, TArray<UObject*> ExcludedCharacters, UConvaiChatbotComponent*& ConvaiCharacter, bool& Found);
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Utilities", meta = (WorldContext = "WorldContextObject"))
static void ConvaiGetLookedAtObjectOrCharacter(UObject* WorldContextObject, APlayerController* PlayerController, float Radius, bool PlaneView, TArray<FConvaiObjectEntry> ListToSearchIn, FConvaiObjectEntry& FoundObjectOrCharacter, bool& Found);
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Utilities", meta = (WorldContext = "WorldContextObject"))
static void ConvaiGetAllPlayerComponents(UObject* WorldContextObject, TArray<class UConvaiPlayerComponent*>& ConvaiPlayerComponents);
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "Convai|Utilities", meta = (WorldContext = "WorldContextObject"))
static void ConvaiGetAllChatbotComponents(UObject* WorldContextObject, TArray<class UConvaiChatbotComponent*>& ConvaiChatbotComponents);
UFUNCTION(BlueprintCallable, Category = "Convai|Settings")
static void SetAPI_Key(FString API_Key);
UFUNCTION(BlueprintPure, Category = "Convai|Settings")
static FString GetAPI_Key();
UFUNCTION(BlueprintCallable, Category = "Convai|Settings")
static void SetAuthToken(FString AuthToken);
UFUNCTION(BlueprintPure, Category = "Convai|Settings")
static FString GetAuthToken();
static TPair<FString, FString> GetAuthHeaderAndKey();
UFUNCTION(BlueprintPure, Category = "Convai|Settings")
static FString GetTestCharacterID();
UFUNCTION(BlueprintPure, Category = "Convai|Settings")
static bool IsNewActionSystemEnabled();
UFUNCTION(BlueprintPure, Category = "Convai|Utilities")
static void GetPluginInfo(FString PluginName, bool& Found, FString& VersionName, FString& EngineVersion, FString& FriendlyName);
UFUNCTION(BlueprintPure, Category = "Convai|Utilities")
static void GetPlatformInfo(FString& EngineVersion, FString& PlatformName);
UFUNCTION(BlueprintPure, Category = "Convai|Utilities")
static TMap<FName, float> MapBlendshapes(const TMap<FName,float>& InputBlendshapes, const TMap<FName, FConvaiBlendshapeParameters>& BlendshapeMap, float GlobalMultiplier, float GlobalOffset);
static TArray<uint8> ExtractPCMDataFromSoundWave(USoundWave* SoundWave, int32& OutSampleRate, int32& OutNumChannels);
static void PCMDataToWav(TArray<uint8> InPCMBytes, TArray<uint8>& OutWaveFileData, int NumChannels, int SampleRate);
static USoundWave* PCMDataToSoundWav(TArray<uint8> InPCMBytes, int NumChannels, int SampleRate);
static USoundWave* WavDataToSoundWave(TArray<uint8> InWavData);
// Writes a USoundWave to a .wav file on disk
UFUNCTION(BlueprintCallable, Category = "Convai|Utilities")
static bool WriteSoundWaveToWavFile(USoundWave* SoundWave, const FString& FilePath);
// Reads a .wav file from disk and creates a USoundWave
UFUNCTION(BlueprintPure, Category = "Convai|Utilities")
static USoundWave* ReadWavFileAsSoundWave(const FString& FilePath);
static void ResampleAudio(float currentSampleRate, float targetSampleRate, int numChannels, bool reduceToMono, int16* currentPcmData, int numSamplesToConvert, TArray<int16>& outResampledPcmData);
static void ResampleAudio(float currentSampleRate, float targetSampleRate, int numChannels, bool reduceToMono, const TArray<int16>& currentPcmData, int numSamplesToConvert, TArray<int16>& outResampledPcmData);
static FString FUTF8ToFString(const char* StringToConvert);
static int LevenshteinDistance(const FString& s, const FString& t);
static TArray<FAnimationFrame> ParseJsonToBlendShapeData(const FString& JsonString);
static bool ParseVisemeValuesToAnimationFrame(const FString& VisemeValuesString, FAnimationFrame& AnimationFrame);
// UFUNCTION(BlueprintCallable, Category = "ActorFuncions", meta = (WorldContext = WorldContextObject))
static AActor* ConvaiCloneActor(AActor* InputActor);
// UFUNCTION(BlueprintPure, Category = "Convai|Utilities|AnimationSequence")
static FString ConvaiAnimationSequenceToJson(const FAnimationSequenceBP& AnimationSequenceBP);
// UFUNCTION(BlueprintPure, Category = "Convai|Utilities|AnimationSequence")
static void ConvaiAnimationSequenceFromJson(const FString& JsonString, FAnimationSequenceBP& AnimationSequenceBP);
};
UCLASS()
class CONVAI_API UConvaiSettingsUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static bool GetParamValueAsString(const FString& paramName, FString& outValue);
static bool GetParamValueAsFloat(const FString& paramName, float& outValue);
static bool GetParamValueAsInt(const FString & paramName, int32 & outValue);
};
UCLASS()
class CONVAI_API UConvaiFormValidation : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static bool ValidateAuthKey(FString API_Key)
{
if ((API_Key.Len()))
{
return true;
}
else
{
CONVAI_LOG(LogTemp, Warning, TEXT("Empty API Key, please add it in Edit->Project Settings->Convai"));
return false;
}
}
static bool ValidateSessionID(FString SessionID)
{
if ((SessionID.Len()))
{
return true;
}
else
{
CONVAI_LOG(ConvaiFormValidationLog, Warning, TEXT("Empty Session ID"));
return false;
}
}
static bool ValidateCharacterID(FString CharacterID)
{
if ((CharacterID.Len()))
{
return true;
}
else
{
CONVAI_LOG(ConvaiFormValidationLog, Warning, TEXT("Empty Character ID"));
return false;
}
}
static bool ValidateInputText(FString InputText)
{
if ((InputText.Len()))
{
return true;
}
else
{
CONVAI_LOG(LogTemp, Warning, TEXT("Empty Input Text"));
return false;
}
}
static bool ValidateVoiceType(FString VoiceType)
{
if ((VoiceType.Len()))
{
return true;
}
else
{
CONVAI_LOG(ConvaiFormValidationLog, Warning, TEXT("Invalid Voice Type"));
return false;
}
}
static bool ValidateBackstory(FString Backstory)
{
if ((Backstory.Len()))
{
return true;
}
else
{
CONVAI_LOG(ConvaiFormValidationLog, Warning, TEXT("Empty Backstory"));
return false;
}
}
static bool ValidateCharacterName(FString CharacterName)
{
if ((CharacterName.Len()))
{
return true;
}
else
{
CONVAI_LOG(ConvaiFormValidationLog, Warning, TEXT("Empty Character Name"));
return false;
}
}
static bool ValidateInputVoice(TArray<uint8> InputVoiceData)
{
if ((InputVoiceData.Num() > 44))
{
return true;
}
else
{
CONVAI_LOG(ConvaiFormValidationLog, Warning, TEXT("Input Voice is too short (less than 44 bytes)"));
return false;
}
}
};
UCLASS()
class UCommandLineUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// Function to check if a flag is present in the command line - Has issue it always returns false
//UFUNCTION(BlueprintCallable, Category = "CommandLine")
static bool IsCommandLineFlagPresent(const FString& Flag);
// Function to get the value of a command line flag as an integer
UFUNCTION(BlueprintCallable, Category = "CommandLine")
static int32 GetCommandLineFlagValueAsInt(const FString& Flag, int32 DefaultValue = 0);
// Function to get the value of a command line flag as a string
UFUNCTION(BlueprintCallable, Category = "CommandLine")
static FString GetCommandLineFlagValueAsString(const FString& Flag, const FString& DefaultValue = TEXT(""));
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "CommandLine")
static FString GetCommandLineFlagValueAsStringNoDefault(const FString& Flag);
};

View File

@@ -0,0 +1,35 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ConvaiDefinitions.h"
#include "LipSyncInterface.generated.h"
DECLARE_DELEGATE(FOnVisemesDataReadySignature);
UINTERFACE()
class CONVAI_API UConvaiLipSyncInterface : public UInterface
{
GENERATED_BODY()
};
class IConvaiLipSyncInterface
{
GENERATED_BODY()
public:
FOnVisemesDataReadySignature OnVisemesDataReady;
virtual void ConvaiInferFacialDataFromAudio(uint8* InPCMData, uint32 InPCMDataSize, uint32 InSampleRate, uint32 InNumChannels) = 0;
virtual void ConvaiStopLipSync() = 0;
virtual TArray<float> ConvaiGetVisemes() = 0;
virtual TArray<FString> ConvaiGetVisemeNames() = 0;
virtual void ConvaiApplyPrecomputedFacialAnimation(uint8* InPCMData, uint32 InPCMDataSize, uint32 InSampleRate, uint32 InNumChannels, FAnimationSequence FaceSequence) = 0;
virtual void ConvaiApplyFacialFrame(FAnimationFrame FaceFrame, float Duration) = 0;
virtual bool RequiresPrecomputedFaceData() = 0;
virtual bool GeneratesVisemesAsBlendshapes() = 0;
virtual TMap<FName, float> ConvaiGetFaceBlendshapes() = 0;
virtual void ForceRecalculateStartTime() = 0;
};

View File

@@ -0,0 +1,100 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "Dom/JsonObject.h"
#include "Runtime/Launch/Resources/Version.h"
#ifdef USE_CONVAI_HTTP
#else
#define USE_CONVAI_HTTP 0
#endif
#if USE_CONVAI_HTTP
// Use ConvaiHttp module
#include "ConvaihttpModule.h"
#include "Interfaces/IConvaihttpRequest.h"
#include "Interfaces/IConvaihttpResponse.h"
#define CONVAI_HTTP_MODULE FConvaihttpModule
#define CONVAI_HTTP_REQUEST_INTERFACE IConvaihttpRequest
#define CONVAI_HTTP_RESPONSE_INTERFACE IConvaihttpResponse
#define CONVAI_HTTP_REQUEST_PTR FConvaihttpRequestPtr
#define CONVAI_HTTP_RESPONSE_PTR FConvaihttpResponsePtr
#define CONVAI_HTTP_PAYLOAD_ARRAY_TYPE TArray64<uint8>
#define CONVAI_HTTP_DOWN_PROGRESS_TYPE uint64
#else
// Use HTTP module
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#define CONVAI_HTTP_MODULE FHttpModule
#define CONVAI_HTTP_REQUEST_INTERFACE IHttpRequest
#define CONVAI_HTTP_RESPONSE_INTERFACE IHttpResponse
#define CONVAI_HTTP_REQUEST_PTR FHttpRequestPtr
#define CONVAI_HTTP_RESPONSE_PTR FHttpResponsePtr
#define CONVAI_HTTP_PAYLOAD_ARRAY_TYPE TArray<uint8>
#if ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4)
#define CONVAI_HTTP_DOWN_PROGRESS_TYPE uint64
#else
#define CONVAI_HTTP_DOWN_PROGRESS_TYPE int32
#endif
#endif
#include "ConvaiAPIBase.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(ConvaiBaseHttpLogs, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FStringHttpResponseCallbackSignature, FString, ResponseString);
namespace ConvaiHttpConstants
{
static const TCHAR* GET = TEXT("GET");
static const TCHAR* POST = TEXT("POST");
static const TCHAR* PUT = TEXT("PUT");
}
//-------------------------------------------Base API class-----------------------------------------
/** Base class for all the Convai API calls */
UCLASS()
class CONVAI_API UConvaiAPIBaseProxy : public UOnlineBlueprintCallProxyBase
{
GENERATED_BODY()
public:
virtual void Activate() override;
protected:
/*IHttp interface*/
virtual void OnHttpRequestComplete(CONVAI_HTTP_REQUEST_PTR Request, CONVAI_HTTP_RESPONSE_PTR Response, bool bWasSuccessful);
/*END IHttp interface*/
virtual bool ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb);
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) { return false; }
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) { return false; }
virtual void HandleSuccess();
virtual void HandleFailure();
public:
FString URL;
FString ResponseString;
CONVAI_HTTP_PAYLOAD_ARRAY_TYPE ResponseData;
};
//--------------------------------------------------------------------------------------------------
/** Base class for all the Convai Asset manager API calls */
UCLASS()
class CONVAI_API UConvaiAPITokenInBodyProxy : public UConvaiAPIBaseProxy
{
GENERATED_BODY()
protected:
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) override;
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) override;
};

View File

@@ -0,0 +1,188 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "RestAPI/ConvaiAPIBase.h"
#include "ConvaiDefinitions.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ConvaiLTMProxy.generated.h"
struct FConvaiSpeakerInfo;
DECLARE_LOG_CATEGORY_EXTERN(LTMHttpLogs, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpeakerIDListHttpResponseCallbackSignature, const TArray<FConvaiSpeakerInfo>&, SpeakerIDs);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpeakerIDHttpResponseCallbackSignature, const FConvaiSpeakerInfo&, SpeakerID);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLTMStatusHttpResponseCallbackSignature, bool, Status);
// Create speaker id
UCLASS()
class CONVAI_API UConvaiCreateSpeakerID : public UConvaiAPIBaseProxy
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FSpeakerIDHttpResponseCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FSpeakerIDHttpResponseCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Create Speaker ID"), Category = "Convai|LTM")
static UConvaiCreateSpeakerID* ConvaiCreateSpeakerIDProxy(FString SpeakerName, FString DeviceId);
protected:
virtual bool ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb) override;
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) override { return false; }
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) override;
virtual void HandleSuccess() override;
virtual void HandleFailure() override;
FString AssociatedSpeakerName;
FString AssociatedDeviceId;
FConvaiSpeakerInfo AssociatedSpeakerInfo;
};
// END create speaker id
// List speaker ids
UCLASS()
class CONVAI_API UConvaiListSpeakerID : public UConvaiAPIBaseProxy
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FSpeakerIDListHttpResponseCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FSpeakerIDListHttpResponseCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai List Speaker IDs"), Category = "Convai|LTM")
static UConvaiListSpeakerID* ConvaiListSpeakerIDProxy();
protected:
virtual bool ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb) override;
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) override { return false; }
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) override { return false; }
virtual void HandleSuccess() override;
virtual void HandleFailure() override;
};
// END list speaker ids
// Delete speaker id
UCLASS()
class CONVAI_API UConvaiDeleteSpeakerID : public UConvaiAPIBaseProxy
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FStringHttpResponseCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FStringHttpResponseCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Delete Speaker ID"), Category = "Convai|LTM")
static UConvaiDeleteSpeakerID* ConvaiDeleteSpeakerIDProxy(FString SpeakerID);
protected:
virtual bool ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb) override;
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) override { return false; }
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) override;
virtual void HandleSuccess() override;
virtual void HandleFailure() override;
FString AssociatedSpeakerID;
};
// END Delete speaker id
// Get LTM status
UCLASS()
class CONVAI_API UConvaiGetLTMStatus : public UConvaiAPIBaseProxy
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FLTMStatusHttpResponseCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FLTMStatusHttpResponseCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Get LTM Status"), Category = "Convai|LTM")
static UConvaiGetLTMStatus* ConvaiGetLTMStatusProxy(FString CharacterID);
protected:
virtual bool ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb) override;
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) override { return false; }
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) override;
virtual void HandleSuccess() override;
virtual void HandleFailure() override;
FString AssociatedCharacterID;
};
// END Get LTM status
// Set LTM status
UCLASS()
class CONVAI_API UConvaiSetLTMStatus : public UConvaiAPIBaseProxy
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FStringHttpResponseCallbackSignature OnSuccess;
UPROPERTY(BlueprintAssignable)
FStringHttpResponseCallbackSignature OnFailure;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Convai Set LTM Status"), Category = "Convai|LTM")
static UConvaiSetLTMStatus* ConvaiSetLTMStatusProxy(FString CharacterID, bool bEnable);
protected:
virtual bool ConfigureRequest(TSharedRef<CONVAI_HTTP_REQUEST_INTERFACE> Request, const TCHAR* Verb) override;
virtual bool AddContentToRequest(CONVAI_HTTP_PAYLOAD_ARRAY_TYPE& DataToSend, const FString& Boundary) override { return false; }
virtual bool AddContentToRequestAsString(TSharedPtr<FJsonObject>& ObjectToSend) override;
virtual void HandleSuccess() override;
virtual void HandleFailure() override;
FString AssociatedCharacterID;
bool bAssociatedEnable;
};
// END Set LTM status
UCLASS()
class CONVAI_API UConvaiLTMUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static bool ParseConvaiSpeakerInfoArray(const FString& JsonString, TArray<FConvaiSpeakerInfo>& OutSpeakerInfoArray);
static bool GetLTMStatus(const FString& JsonString, bool& bEnabled);
};

View File

@@ -0,0 +1,56 @@
#pragma once
#include "CoreMinimal.h"
#include "ConvaiURL.generated.h"
UENUM(BlueprintType)
enum class EConvaiEndpoint : uint8
{
NewSpeaker UMETA(DisplayName = "New Speaker"),
SpeakerIDList UMETA(DisplayName = "Speaker ID List"),
DeleteSpeakerID UMETA(DisplayName = "Delete Speaker ID"),
ReferralSourceStatus UMETA(DisplayName = "Referral Source Status"),
UpdateReferralSource UMETA(DisplayName = "Update Referral Source"),
UserAPIUsage UMETA(DisplayName = "User API Usage"),
CharacterUpdate UMETA(DisplayName = "Character Update"),
CharacterGet UMETA(DisplayName = "Character Get"),
ListCharacterSections UMETA(DisplayName = "List Character Sections"),
ListCharacterTriggers UMETA(DisplayName = "List Character Triggers")
};
UCLASS()
class CONVAI_API UConvaiURL : public UObject
{
GENERATED_BODY()
public:
static FString GetEndpoint(EConvaiEndpoint Endpoint);
/** Get the base URL for a specific environment (beta or prod) */
static FString GetBaseURL(bool bUseBeta = false);
/** Get the full URL for a specific API endpoint */
static FString GetFullURL(const FString& ApiPath, bool bUseBeta = false);
static FString GetFormattedBaseURL(const FString& Subdomain);
/** Initialize URL configuration from command line or settings */
static void InitializeURLConfig();
private:
static const TCHAR BETA_SUBDOMAIN[];
static const TCHAR PROD_SUBDOMAIN[];
static const TCHAR BASE_URL[];
static const TCHAR BASE_URL_FORMAT[];
static const TCHAR LTM_SUBDOMAIN[];
static const TCHAR USER_SUBDOMAIN[];
static const TCHAR CHARACTER_SUBDOMAIN[];
static const TCHAR NARRATIVE_DESIGN_SUBDOMAIN[];
static TArray<EConvaiEndpoint> BetaEndpoints;
static FString CustomBetaBaseURL;
static FString CustomProdBaseURL;
static bool bURLConfigInitialized;
};

View File

@@ -0,0 +1,505 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Misc/SecureHash.h"
template< typename DataType >
class TRingBuffer
{
public:
/**
* Constructor
*/
TRingBuffer(uint32 BufferDataSize);
TRingBuffer();
/**
* Default Destructor
*/
~TRingBuffer();
void Init(uint32 InBufferDataSize);
/**
* Clears memory and indexes
*/
void Empty();
/**
* Pushes a data word to the end of the FIFO. WILL OVERWRITE OLDEST if full.
* @param Val The data word to push
*/
void Enqueue(const DataType& Val);
/**
* Pushes a buffer of data words to the end of the FIFO. WILL OVERWRITE OLDEST if full.
* @param ValBuf The buffer pointer
* @param BufLen The length to copy
*/
void Enqueue(const DataType* ValBuf, const uint32& BufLen);
/**
* Take the next data word from the FIFO buffer.
* @param OUT ValOut The variable to receive the data word
* @return true if the buffer was not empty, false otherwise.
*/
bool Dequeue(DataType& ValOut);
/**
* Take the next set of data words from the FIFO buffer.
* @param ValBuf The buffer to receive the data
* @param BufLen The number of words requested
* @return the number of words actually copied
*/
uint32 Dequeue(DataType* ValBuf, const uint32& BufLen);
/**
* Get the next data word from the FIFO buffer without removing it.
* @param OUT ValOut The variable to receive the data word
* @return true if the buffer was not empty, false otherwise.
*/
bool Peek(DataType& ValOut) const;
/**
* Get the next set of data words from the FIFO buffer without removing them.
* @param ValBuf The buffer to receive the data
* @param BufLen The number of words requested
* @return the number of words actually copied
*/
uint32 Peek(DataType* ValBuf, const uint32& BufLen) const;
/**
* Compare the memory in the FIFO to the memory in the given buffer
* @param SerialBuffer The buffer containing data to compare
* @param CompareLen The number of words to compare
* @return < 0 if data < SerialBuffer.. 0 if data == SerialBuffer... > 0 if data > SerialBuffer
*/
int32 SerialCompare(const DataType* SerialBuffer, uint32 CompareLen) const;
/**
* Get the SHA1 hash for the data currently in the FIFO
* @param OutHash Receives the SHA hash
*/
void GetShaHash(FSHAHash& OutHash) const;
/**
* Serializes the internal buffer into the given buffer
* @param SerialBuffer A preallocated buffer to copy data into
*/
void Serialize(DataType* SerialBuffer) const;
/**
* Square bracket operators for accessing data in the buffer by FIFO index. [0] returns the next entry in the FIFO that would get provided by Dequeue or Peek.
* @param Index The index into the FIFO buffer
* @return The data word
*/
FORCEINLINE const DataType& operator[](const int32& Index) const;
FORCEINLINE DataType& operator[](const int32& Index);
/**
* Gets the last data word in the FIFO (i.e. most recently pushed).
* @return The last data word
*/
FORCEINLINE const DataType& Top() const;
FORCEINLINE DataType& Top();
/**
* Gets the first data word in the FIFO (i.e. oldest).
* @return The first data word
*/
FORCEINLINE const DataType& Bottom() const;
FORCEINLINE DataType& Bottom();
/**
* Gets the buffer index that the last data word is stored in.
* @return The index of Top()
*/
FORCEINLINE uint32 TopIndex() const;
/**
* Gets the buffer index that the first data word is stored in.
* @return The index of Bottom()
*/
FORCEINLINE uint32 BottomIndex() const;
/**
* Gets the buffer index that the next enqueued word will get stored in.
* @return The index that the next enqueued word will have
*/
FORCEINLINE uint32 NextIndex() const;
/**
* Gets the size of the data buffer.
* @return Template arg BufferDataSize
*/
FORCEINLINE uint32 RingDataSize() const;
/**
* Gets the number of words currently in the FIFO.
* @return data size
*/
FORCEINLINE uint32 RingDataUsage() const;
/**
* Gets the total number of words that have been pushed through this buffer since clearing
* @return total length of data passed through
*/
FORCEINLINE uint64 TotalDataPushed() const;
/**
* Maximum size the buffer can grow to (50 MB by default)
*/
static constexpr uint32 MaxBufferSize = 1024 * 1024 * 50;
/**
* Resizes the buffer to a new size, preserving existing data
* @param NewBufferSize The new size of the buffer
* @return true if resize was successful, false otherwise
*/
bool Resize(uint32 NewBufferSize);
private:
//TRingBuffer();
// Buffer size
uint32 BufferDataSize;
// The data memory
DataType* Data;
// Value keeping track of free space
uint32 NumDataAvailable;
// Value keeping track of the next data index
uint32 DataIndex;
// Value to keep track of total amount of data Enqueued
uint64 TotalNumDataPushed;
};
/* TRingBuffer implementation
*****************************************************************************/
template< typename DataType >
TRingBuffer< DataType >::TRingBuffer(uint32 InBufferDataSize)
: BufferDataSize(InBufferDataSize)
{
Data = new DataType[BufferDataSize];
Empty();
}
template< typename DataType >
void TRingBuffer< DataType >::Init(uint32 InBufferDataSize)
{
BufferDataSize = InBufferDataSize;
Data = new DataType[BufferDataSize];
Empty();
}
template< typename DataType >
TRingBuffer< DataType >::TRingBuffer()
{
}
template< typename DataType >
TRingBuffer< DataType >::~TRingBuffer()
{
delete[] Data;
}
template< typename DataType >
void TRingBuffer< DataType >::Empty()
{
FMemory::Memzero(Data, sizeof(DataType)* BufferDataSize);
NumDataAvailable = 0;
DataIndex = 0;
TotalNumDataPushed = 0;
}
template< typename DataType >
void TRingBuffer< DataType >::Enqueue(const DataType& Val)
{
Data[DataIndex++] = Val;
DataIndex %= BufferDataSize;
++TotalNumDataPushed;
NumDataAvailable = FMath::Clamp< uint32 >(NumDataAvailable + 1, 0, BufferDataSize);
}
template< typename DataType >
void TRingBuffer< DataType >::Enqueue(const DataType* ValBuf, const uint32& BufLen)
{
// Check if we need to resize
if (BufLen > BufferDataSize || NumDataAvailable + BufLen > BufferDataSize)
{
// Calculate new size (double the current size or enough to fit the new data, whichever is larger)
uint32 NewSize = FMath::Max(BufferDataSize * 2, NumDataAvailable + BufLen);
// Cap the maximum size
NewSize = FMath::Min(NewSize, MaxBufferSize);
// Try to resize
bool ResizeSuccess = Resize(NewSize);
// If resize failed and the buffer is too small for the new data
if (!ResizeSuccess && BufLen > BufferDataSize)
{
// We can't fit the entire new buffer, so we'll copy as much as we can
uint32 AmountToCopy = BufferDataSize;
// Copy from the end of the input buffer to get the most recent data
const DataType* SourcePtr = ValBuf + (BufLen - AmountToCopy);
// Clear the buffer and copy what we can
Empty();
FMemory::Memcpy(Data, SourcePtr, sizeof(DataType) * AmountToCopy);
NumDataAvailable = AmountToCopy;
DataIndex = 0;
TotalNumDataPushed += AmountToCopy;
return;
}
// If resize failed but we can fit some data by overwriting old data
else if (!ResizeSuccess)
{
// Calculate how much old data we need to discard
uint32 SpaceNeeded = NumDataAvailable + BufLen - BufferDataSize;
// Discard oldest data
NumDataAvailable -= SpaceNeeded;
// Continue with normal enqueue below
}
}
// Original enqueue logic
check(BufLen <= BufferDataSize);
const uint32 FirstPartLen = BufferDataSize - DataIndex;
FMemory::Memcpy(Data + DataIndex, ValBuf, sizeof(DataType) * FMath::Min(FirstPartLen, BufLen));
if (FirstPartLen < BufLen)
{
FMemory::Memcpy(Data, ValBuf + FirstPartLen, sizeof(DataType) * (BufLen - FirstPartLen));
}
DataIndex += BufLen;
DataIndex %= BufferDataSize;
TotalNumDataPushed += BufLen;
NumDataAvailable = FMath::Clamp<uint32>(NumDataAvailable + BufLen, 0, BufferDataSize);
}
template< typename DataType >
bool TRingBuffer< DataType >::Dequeue(DataType& ValOut)
{
if (Peek(ValOut))
{
NumDataAvailable = FMath::Clamp< uint32 >(NumDataAvailable - 1, 0, BufferDataSize);
return true;
}
else
{
return false;
}
}
template< typename DataType >
uint32 TRingBuffer< DataType >::Dequeue(DataType* ValBuf, const uint32& BufLen)
{
uint32 DataProvided = 0;
if (ValBuf != nullptr)
{
// Normal case: copy data to the provided buffer
DataProvided = Peek(ValBuf, BufLen);
}
else
{
// Special case: just discard data without copying
DataProvided = FMath::Min(BufLen, NumDataAvailable);
}
// Update the available data count
NumDataAvailable -= DataProvided;
return DataProvided;
}
template< typename DataType >
bool TRingBuffer< DataType >::Peek(DataType& ValOut) const
{
if (NumDataAvailable > 0)
{
ValOut = Bottom();
return true;
}
else
{
return false;
}
}
template< typename DataType >
uint32 TRingBuffer< DataType >::Peek(DataType* ValBuf, const uint32& BufLen) const
{
const uint32 DataProvided = FMath::Min(BufLen, NumDataAvailable);
const uint32 BottomIdx = BottomIndex();
const uint32 BottomPartLen = BufferDataSize - BottomIdx;
FMemory::Memcpy(ValBuf, Data + BottomIdx, sizeof(DataType)* FMath::Min(BottomPartLen, DataProvided));
if (BottomPartLen < DataProvided)
{
FMemory::Memcpy(ValBuf + BottomPartLen, Data, sizeof(DataType)* (DataProvided - BottomPartLen));
}
return DataProvided;
}
template< typename DataType >
int32 TRingBuffer< DataType >::SerialCompare(const DataType* SerialBuffer, uint32 CompareLen) const
{
check(CompareLen <= BufferDataSize);
const uint32 FirstPartLen = BufferDataSize - DataIndex;
int32 FirstCmp = FMemory::Memcmp(Data + BottomIndex(), SerialBuffer, sizeof(DataType)* FMath::Min(FirstPartLen, CompareLen));
if (FirstCmp != 0 || FirstPartLen >= CompareLen)
{
return FirstCmp;
}
return FMemory::Memcmp(Data, &SerialBuffer[FirstPartLen], sizeof(DataType)* (CompareLen - FirstPartLen));
}
template< typename DataType >
void TRingBuffer< DataType >::GetShaHash(FSHAHash& OutHash) const
{
const uint32 FirstPartLen = FMath::Min(BufferDataSize - DataIndex, NumDataAvailable);
FSHA1 Sha;
Sha.Update(Data + BottomIndex(), sizeof(DataType) * FirstPartLen);
if (FirstPartLen < NumDataAvailable)
{
Sha.Update(Data, sizeof(DataType) * (NumDataAvailable - FirstPartLen));
}
Sha.Final();
Sha.GetHash(OutHash.Hash);
}
template< typename DataType >
void TRingBuffer< DataType >::Serialize(DataType* SerialBuffer) const
{
const uint32 BottomIdx = BottomIndex();
const uint32 BottomPartLen = BufferDataSize - BottomIdx;
FMemory::Memcpy(SerialBuffer, Data + BottomIdx, sizeof(DataType)* BottomPartLen);
FMemory::Memcpy(SerialBuffer + BottomPartLen, Data, sizeof(DataType)* (BufferDataSize - BottomPartLen));
}
template< typename DataType >
FORCEINLINE const DataType& TRingBuffer< DataType >::operator[](const int32& Index) const
{
return Data[(BottomIndex() + Index) % BufferDataSize];
}
template< typename DataType >
FORCEINLINE DataType& TRingBuffer< DataType >::operator[](const int32& Index)
{
return Data[(BottomIndex() + Index) % BufferDataSize];
}
template< typename DataType >
FORCEINLINE const DataType& TRingBuffer< DataType >::Top() const
{
return Data[TopIndex()];
}
template< typename DataType >
FORCEINLINE DataType& TRingBuffer< DataType >::Top()
{
return Data[TopIndex()];
}
template< typename DataType >
FORCEINLINE const DataType& TRingBuffer< DataType >::Bottom() const
{
return Data[BottomIndex()];
}
template< typename DataType >
FORCEINLINE DataType& TRingBuffer< DataType >::Bottom()
{
return Data[BottomIndex()];
}
template< typename DataType >
FORCEINLINE uint32 TRingBuffer< DataType >::TopIndex() const
{
return (DataIndex + BufferDataSize - 1) % BufferDataSize;
}
template< typename DataType >
FORCEINLINE uint32 TRingBuffer< DataType >::BottomIndex() const
{
return (DataIndex + BufferDataSize - NumDataAvailable) % BufferDataSize;
}
template< typename DataType >
FORCEINLINE uint32 TRingBuffer< DataType >::NextIndex() const
{
return DataIndex;
}
template< typename DataType >
FORCEINLINE uint32 TRingBuffer< DataType >::RingDataSize() const
{
return BufferDataSize;
}
template< typename DataType >
FORCEINLINE uint32 TRingBuffer< DataType >::RingDataUsage() const
{
return NumDataAvailable;
}
template< typename DataType >
FORCEINLINE uint64 TRingBuffer< DataType >::TotalDataPushed() const
{
return TotalNumDataPushed;
}
template< typename DataType >
bool TRingBuffer< DataType >::Resize(uint32 NewBufferSize)
{
// Don't resize if new size is smaller than current usage
if (NewBufferSize < NumDataAvailable)
{
return false;
}
// Create a new buffer
DataType* NewData = new DataType[NewBufferSize];
if (!NewData)
{
return false;
}
// Copy existing data to the new buffer
if (NumDataAvailable > 0)
{
// Get the current data in sequential order
const uint32 BottomIdx = BottomIndex();
const uint32 BottomPartLen = FMath::Min(BufferDataSize - BottomIdx, NumDataAvailable);
// Copy first part (from bottom index to end of buffer)
FMemory::Memcpy(NewData, Data + BottomIdx, sizeof(DataType) * BottomPartLen);
// Copy second part (from start of buffer if wrapped)
if (BottomPartLen < NumDataAvailable)
{
FMemory::Memcpy(NewData + BottomPartLen, Data, sizeof(DataType) * (NumDataAvailable - BottomPartLen));
}
}
// Clean up old buffer and update state
delete[] Data;
Data = NewData;
BufferDataSize = NewBufferSize;
// Reset the data index to start at 0
DataIndex = NumDataAvailable % BufferDataSize;
return true;
}

View File

@@ -0,0 +1,82 @@
// ConvaiLogger.h
#pragma once
#include "CoreMinimal.h"
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "Containers/Queue.h"
#include "Misc/DateTime.h"
#include "Misc/Paths.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/PlatformFile.h"
#include "HAL/ThreadSafeBool.h"
#include "HAL/Event.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ConvaiLogger.generated.h"
// In editor: log to both UE4s log window AND your file logger
#define CONVAI_LOG(Category, Verbosity, Format, ...) \
UE_LOG(Category, Verbosity, Format, ##__VA_ARGS__); \
FConvaiLogger::Get().Log( \
FString::Printf( \
TEXT(#Category) TEXT(" : ") TEXT(#Verbosity) TEXT(" : ") Format, \
##__VA_ARGS__ \
) \
);
/**
* Asynchronous, file-based logger singleton using IFileHandle in append mode.
*/
class CONVAI_API FConvaiLogger final : public FRunnable
{
public:
static FConvaiLogger& Get();
void Log(const FString& Message);
// FRunnable interface
virtual uint32 Run() override;
virtual void Stop() override;
private:
FConvaiLogger();
virtual ~FConvaiLogger() override;
// start/stop thread
void StartThread();
void ShutdownThread();
FRunnableThread* Thread;
TQueue<FString, EQueueMode::Mpsc> MessageQueue;
FEvent* WakeEvent;
FString LogFilePath;
FThreadSafeBool bStopping;
};
// BP function lib for logging
UENUM(BlueprintType)
enum class EC_LogLevel : uint8
{
Verbose UMETA(DisplayName="Verbose"),
Log UMETA(DisplayName="Log"),
Warning UMETA(DisplayName="Warning"),
Error UMETA(DisplayName="Error"),
Fatal UMETA(DisplayName="Fatal")
};
UCLASS()
class CONVAI_API UConvaiBlueprintLogger : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Logs a message with context of which Blueprint object called this.
* @param WorldContextObject automatically filled by Blueprint
* @param Verbosity choose your verbosity level
* @param Message the text you want to log
*/
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", DisplayName="ConvaiLog"), Category="Convai|Logging")
static void C_ConvaiLog(UObject* WorldContextObject, EC_LogLevel Verbosity, const FString& Message);
};

View File

@@ -0,0 +1,140 @@
// Copyright 2022 Convai Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ConvaiDefinitions.h"
#include "Engine/Texture.h"
#include "VisionInterface.generated.h"
UENUM(BlueprintType)
enum class ETextureSourceType : uint8
{
Texture2D UMETA(DisplayName = "Texture2D"),
RenderTarget2D UMETA(DisplayName = "RenderTarget2D")
};
UENUM(BlueprintType)
enum class EVisionState : uint8
{
Stopped UMETA(DisplayName = "Stopped"), // Vision system is inactive and stopped
Starting UMETA(DisplayName = "Starting"), // Vision system is initializing
Capturing UMETA(DisplayName = "Capturing"), // Vision system is capturing data
Stopping UMETA(DisplayName = "Stopping"), // Vision system is in the process of stopping
Paused UMETA(DisplayName = "Paused"), // Vision system is paused and not capturing
};
// Delegate to handle state change notifications
DECLARE_DELEGATE_OneParam(FOnVisionStateChanged, EVisionState);
// Delegate to handle when the first frame has been captured
DECLARE_DELEGATE(FOnFirstFrameCaptured);
// Delegate to handle when frames have stopped capturing
DECLARE_DELEGATE(FOnFramesStopped);
UINTERFACE()
class CONVAI_API UConvaiVisionInterface : public UInterface
{
GENERATED_BODY()
};
class IConvaiVisionInterface
{
GENERATED_BODY()
public:
// Delegate called when the vision state changes
FOnVisionStateChanged OnVisionStateChanged;
// Delegate called when the first frame is captured
FOnFirstFrameCaptured OnFirstFrameCaptured;
// Delegate called when frame capturing stops
FOnFramesStopped OnFramesStopped;
/**
* Starts the vision component.
* This will transition the state from Stopped or Paused to Capturing.
*/
virtual void Start() = 0;
/**
* Stops the vision component.
* This will transition the state from Capturing or Paused to Stopped.
*/
virtual void Stop() = 0;
/**
* Retrieves the current vision state.
* @return The current EVisionState of the component.
*/
virtual EVisionState GetState() const = 0;
/**
* Sets the maximum frames per second (FPS) that the vision component will capture.
* @param MaxFPS The maximum FPS value to set.
*/
virtual void SetMaxFPS(int MaxFPS) = 0;
/**
* Gets the current maximum FPS setting.
* @return The maximum FPS setting.
*/
virtual int GetMaxFPS() const = 0;
/**
* Checks if compressed image data is already available (e.g., received from an external source).
* @return True if compressed data is available, false otherwise.
*/
virtual bool IsCompressedDataAvailable() const = 0;
/**
* Retrieves the pre-existing compressed image data (if available).
* @param width The width of the compressed image.
* @param height The height of the compressed image.
* @param data The array to store the compressed image data.
* @return True if compressed data was successfully retrieved, false otherwise.
*/
virtual bool GetCompressedData(int& width, int& height, TArray<uint8>& data) = 0;
/**
* Captures the current frame in a compressed format with a specific compression ratio.
* @param width The width of the captured image, populated by the function.
* @param height The height of the captured image, populated by the function.
* @param data An array to store the compressed image data, populated by the function.
* @param ForceCompressionRatio A specific compression ratio to apply during capture.
* @return True if capture succeeded, false otherwise.
*/
virtual bool CaptureCompressed(int& width, int& height, TArray<uint8>& data, float ForceCompressionRatio) = 0;
/**
* Captures the current frame in raw format (uncompressed).
* @param width The width of the captured image, populated by the function.
* @param height The height of the captured image, populated by the function.
* @param data An array to store the raw image data, populated by the function.
* @return True if capture succeeded, false otherwise.
*/
virtual bool CaptureRaw(int& width, int& height, TArray<uint8>& data) = 0;
/**
* Retrieves the texture of the captured image based on the source type.
* @param TextureSourceType The type of texture source (Texture2D or RenderTarget2D).
* @return A pointer to the UTexture containing the captured image.
*/
virtual UTexture* GetImageTexture(ETextureSourceType& TextureSourceType) = 0;
/**
* Retrieves the last error message encountered by the vision component.
* @return The last error message as a string.
*/
virtual FString GetLastErrorMessage() const = 0;
/**
* Retrieves the last error code encountered by the vision component.
* @return The last error code.
*/
virtual int GetLastErrorCode() const = 0;
};