UE C++ Windows プラットフォームは iFLYTEK 音声合成インターフェイスを呼び出します
環境設定
- Windows プラットフォーム用 Xunfei Speech Synthesis の C++ バージョン SDK (lib ライブラリ ファイルと dll ダイナミック リンク ライブラリを含む) をダウンロードします。
- UE プロジェクトの下に新しい ThirdParty/msc ディレクトリを作成し、そこに lib ライブラリ ファイルと dll ダイナミック リンク ライブラリを配置します。
- ライブラリ ファイルのパスを [PROJECT].Build.cs ファイルに追加します
- AActor から継承した新しい UE C++ クラス ASpeech を作成し、CPP ファイルをインポート Xunfei ライブラリ
ディレクトリ構造
[PROJECT].Build.cs ファイルにライブラリ ファイル パスを追加
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
string MSCPath = Path.Combine(ThirdPartyPath, "msc/");
PublicIncludePaths.AddRange(new string[] {
Path.Combine(MSCPath, "Includes") });
PublicSystemLibraryPaths.Add(Path.Combine(MSCPath, "Libraries"));
Speech.cpp ファイルはライブラリ ファイルをインポートします
#include "Speech.h"
#include "qtts.h"
#include "msp_cmn.h"
#include "msp_errors.h"
#include "Kismet/GameplayStatics.h"
using namespace Audio;
#ifdef _WIN64
#pragma comment(lib,"msc_x64.lib")//x64
#else
#pragma comment(lib,"msc.lib")//x86
#endif
Xunfei 音声インターフェイスに電話をかける
Xunfei 音声合成インターフェイスは主に以下で構成されます。
MSPLogin
QTTSSessionBegin
QTTSTextPut
QTTSAudioGet
QTTSSessionEnd
MSPLogout
このうち、MSPLogin と MSPLogout はプログラムの最初と最後に 1 回呼び出すことができます。
音声を合成するたびに、QTTSTextPut を呼び出し、ループ内で QTTSAudioGet を呼び出して、すべてのデータが受信されるまで継続的に合成音声データを取得し、QTTSSessionEnd を呼び出してこの音声合成タスクを終了します。コードは次のとおりです
。
- 初期化
bool ASpeech::Init(const FString& params) {
// Init TTS
int ret = MSP_SUCCESS;
ret = MSPLogin(NULL, NULL, TCHAR_TO_UTF8(*params)); //第一个参数是用户名,第二个参数是密码,第三个参数是登录参数,用户名和密码可在http://www.xfyun.cn注册获取
if (MSP_SUCCESS != ret)
{
FCString::Sprintf(_debug_string_buff, TEXT("MSPLogin failed, error code: %d."), ret);
ScreenMsg(_debug_string_buff);
bInited = false;
}
else {
bInited = true;
}
return bInited;
}
- テキスト読み上げ
bool ASpeech::Text2Speech(const FString& text, const FString& params)
{
if (bGenerating) {
ScreenMsg(TEXT("Failed: Audio generation in progress!"));
return false;
}
if (text.IsEmpty() || params.IsEmpty()) {
ScreenMsg(TEXT("Failed: Parameter cannot be empty!"));
return false;
}
// Init params
int ret = -1;
sessionID = NULL;
bGenerating = false;
GenerateIndex = 0;
DataLength = 0;
audioData.SetNumUninitialized(0);
bPlaying = false;
PlayTime = UGameplayStatics::GetTimeSeconds(GWorld);
DataTime = 0;
/* 开始合成 */
sessionID = QTTSSessionBegin(TCHAR_TO_UTF8(*params), &ret);
if (MSP_SUCCESS != ret)
{
FCString::Sprintf(_debug_string_buff, TEXT("QTTSSessionBegin failed, error code: %d."), ret);
ScreenMsg(_debug_string_buff);
return false;
}
const char* p_text = TCHAR_TO_UTF8(*text);
ret = QTTSTextPut(sessionID, p_text, (unsigned int)strlen(p_text), NULL);
if (MSP_SUCCESS != ret)
{
FCString::Sprintf(_debug_string_buff, TEXT("QTTSTextPut failed, error code: %d."), ret);
ScreenMsg(_debug_string_buff);
QTTSSessionEnd(sessionID, "TextPutError");
return false;
}
bGenerating = true;
return true;
}
- Tickループで音声データを取得する
void ASpeech::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
/* 获取合成音频 */
if (bGenerating && NULL != sessionID) {
int ret = -1;
unsigned int len = 0;
int synth_status = MSP_TTS_FLAG_STILL_HAVE_DATA;
const void* data = QTTSAudioGet(sessionID, &len, &synth_status, &ret);
if (MSP_SUCCESS == ret) {
if (len > 0) {
uint8 *p_data = (uint8 *)data;
for (unsigned int i = 0; i < len; ++i) {
audioData.Add(p_data[i]);
}
DataLength += len;
// Play audio
if (0 == GenerateIndex) {
// 第一次接收到语音数据,同步开始播放
if (NULL != AudioComponent) {
AudioComponent->Play();
bPlaying = true;
PlayTime = UGameplayStatics::GetTimeSeconds(GWorld);
}
}
++GenerateIndex;
payloadReceivedVoiceData(p_data, len); // 装在数据到播放Wave数据区
OnAudioGet(); // 蓝图调用通知
}
if (MSP_TTS_FLAG_DATA_END == synth_status) {
/* 合成完毕 */
ret = QTTSSessionEnd(sessionID, "Normal");
if (MSP_SUCCESS != ret)
{
FCString::Sprintf(_debug_string_buff, TEXT("QTTSSessionEnd failed, error code: %d."), ret);
ScreenMsg(_debug_string_buff);
}
bGenerating = false;
sessionID = NULL;
}
}
else {
/* 合成失败 */
FCString::Sprintf(_debug_string_buff, TEXT("QTTSAudioGet failed, error code: %d."), ret);
ScreenMsg(_debug_string_buff);
QTTSSessionEnd(sessionID, "AudioGetError");
bGenerating = false;
sessionID = NULL;
}
}
}
- EndPlay終了時の初期化解除
void ASpeech::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Uninit();
Super::EndPlay(EndPlayReason);
}
void ASpeech::Uninit() {
MSPLogout();
bInited = false;
}
音声データを再生する
合成された音声データは PCM 形式であり、SoundWaveProcedural および AudioComponent を通じてロードして再生する必要があります。
- SoundWaveProceduralはオーディオデータを動的にロードできるオーディオソースです
- AudioComponent は再生コンポーネントです。
SoundWaveProcedural コンポーネントと AudioComponent コンポーネントを Speech Actor に追加します
。 1. コンストラクターで AudioComponent コンポーネントを作成します。
ASpeech::ASpeech()
:
SoundWaveProcedural(NULL),
NumChannels(1),
NumSamples(samples_per_sec),
SampleRate(samples_per_sec)
{
AudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("Audio"));
AudioComponent->SetupAttachment(GetRootComponent());
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
2. BeginPlay関数でSoundWaveProceduralオブジェクトを初期化し、AudioComponentの再生ソースとして設定します。
void ASpeech::BeginPlay()
{
Super::BeginPlay();
// Init audio component
AudioComponent->bAutoActivate = true;
AudioComponent->bAlwaysPlay = true;
AudioComponent->PitchMultiplier = 1.0f;
AudioComponent->VolumeMultiplier = 1.0f;
AudioComponent->bIsUISound = false;
AudioComponent->AttenuationSettings = nullptr;
AudioComponent->bOverrideAttenuation = false;
AudioComponent->bAllowSpatialization = false;
// Init sound wave procedural
SoundWaveProcedural = NewObject<USoundWaveProcedural>();
SoundWaveProcedural->SetSampleRate(SampleRate);
SoundWaveProcedural->NumChannels = NumChannels;
SoundWaveProcedural->Duration = INDEFINITELY_LOOPING_DURATION;
SoundWaveProcedural->SoundGroup = SOUNDGROUP_Default;
SoundWaveProcedural->bLooping = false;
SoundWaveProcedural->bProcedural = true;
SoundWaveProcedural->Pitch = 1.0f;
SoundWaveProcedural->Volume = 1.0f;
SoundWaveProcedural->AttenuationSettings = nullptr;
SoundWaveProcedural->bDebug = true;
SoundWaveProcedural->VirtualizationMode = EVirtualizationMode::PlayWhenSilent;
// Set audio component source
AudioComponent->SetSound(SoundWaveProcedural);
Init(TEXT("appid = 55xxxx45, work_dir = ."));
}
3. Ticks ループ関数が QTTSAudioGet を通じてデータを取得したら、そのデータを SoundWaveProcedural オブジェクトにロードします。
void ASpeech::payloadReceivedVoiceData(const uint8 *Data, int32 DataSize)
{
if (NULL == AudioComponent || NULL == SoundWaveProcedural) {
ScreenMsg(TEXT("Error: The sound playback component is empty!"));
return;
}
SoundWaveProcedural->QueueAudio(Data, DataSize);
}
4. 初めてデータをロードするときに、オーディオの再生を同期して開始できます
// Play audio
if (0 == GenerateIndex) {
// 第一次接收到语音数据,同步开始播放
if (NULL != AudioComponent) {
AudioComponent->Play();
bPlaying = true;
PlayTime = UGameplayStatics::GetTimeSeconds(GWorld);
}
}
++GenerateIndex;
payloadReceivedVoiceData(p_data, len); // 装在数据到播放Wave数据区
出力EXE
EXE をエクスポートするときは、ThirdParty の DLL を出力ディレクトリにコピーすることに注意してください。UE の出力プログラムは、ThirdParty の DLL ファイルを自動的にコピーしません。コピー ファイルのパスは次のとおりです。プログラムの
スクリーン
ショット