본문 바로가기

GameDevelopmentDiary/UnrealDiary

언리얼4 립싱크(Text To Speech) 구현 방법 Part4 (완성)

1. 실시간 OVRLipSyncFrameSequence 생성
이전에 Runtime Audio Importer 플러그인과 RawData 상태의 소리값을 사용해 SoundWave 객체를 만들고 목소리를 들어봤었다. 그리고 이제 또 립싱크를 위해서는 OVRLipSyncFrameSequence 객체가 필요하다.


그런데 OVRLipSyncFrameSequence 객체로 변환하는 방법이 에디터의 콘텐츠 브라우저에서 진행 되었었다.

입력되는 텍스트를 매번 파일로 만들고 에디터에서 다시 변환한다면 매우 비효율적이다(는 주관적 생각). 

 

때문에 해당 플러그인에서 시퀀스 생성 부분을 실행 중에도 사용할 수 있도록 해보자.

 

<OvrLipSync 플러그인 시퀀스 생성부분>

시퀀스 생성 노드를 바로 사용할 수 있다면 좋겠지만, 해당 함수는 Private 폴더에 있고 파일을 생성할 필요도 없기에 필요부분을 주욱 복사해오자. 위의 함수와 DecompressSoundWave 함수만 가져오면 된다.

  • 개인적으로 필요한 부분이 있다면 수정하자.
    이 글에서는 ActorComponent 클래스의 함수로 만들었다. (AzureTTS)
    또 매개변수를 UAsset 말고 바로 USoundWave 타입으로 직접 받는 함수를 추가했고, 완성된 시퀀스를 파일 대신 해당 컴포넌트의 프로퍼티로 보관했다.

<OVRLipSyncProcessSoundWave 시작 부분>
<위의 OVRLipSyncProcessSoundWave 종료 부분>

  • 그러면 이제 Runtime Audio Importer 플러그인을 통해 얻었던 SoundWaveRef 리턴값을 위에서 만든 함수의 매개변수로 담을 수 있다. 하지만 이와 같은 경우 플레이 중 ovrlipsyncshim.cpp 파일은 언급하며 에디터가 뻗어버릴 수 있다.

<오류 발생 가능한 경우>

 

2.데이터 형변환(TArray<uint8> to USoundWave)
위에 언급된 오류의 정확한 원인은 파악하지 못했다. 다만, 직접 형변환을 시도한 경우는 문제가 발생하지 않았다.

&lt;Runtime Audio Importer 플러그인을 사용하지 않은 경우&gt;

bool UAzureTTSComponent::ConvertSoundData(TArray<uint8> Data)
{
	FWaveModInfo WaveInfo;
	if (WaveInfo.ReadWaveInfo(Data.GetData(), Data.Num())) {
		USoundWave* sw = NewObject<USoundWave>();
		int32 DurationDiv = *WaveInfo.pChannels * (*WaveInfo.pBitsPerSample / 8.f) * *WaveInfo.pSamplesPerSec;

		/* Null 필터 */
		{
			if (!sw) {
				UE_LOG(LogTemp, Warning, TEXT("Null Sound Wave"));
				return false;
			}

			if (DurationDiv <= 0)
			{
				UE_LOG(LogTemp, Warning, TEXT("Null Too small than DurationDiv"));
				return false;
			}
		}

		/* rawFile 값을 USoundWave 타입으로 형변환 */
		sw->Duration = WaveInfo.SampleDataSize / DurationDiv;
		sw->SetSampleRate(*WaveInfo.pSamplesPerSec); // 해당 프로퍼티가 클 수록 소리가 빨라짐.
		sw->NumChannels = *WaveInfo.pChannels;
		sw->RawPCMDataSize = WaveInfo.SampleDataSize;
		sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
		FMemory::Memmove(sw->RawPCMData, Data.GetData(), Data.Num());
		sw->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
		sw->InvalidateCompressedData();

		sw->RawData.Lock(LOCK_READ_WRITE);
		void* LockedData = sw->RawData.Realloc(Data.Num());
		FMemory::Memcpy(LockedData, Data.GetData(), Data.Num());
		sw->RawData.Unlock();

		LastSoundWave = sw;
	}
	return true;
}

 

 

3. TTS 립싱크 정리

메타휴먼 액터의 블루프린트 구조를 아래와 같이 만들고 립싱크 모습을 확인했다.

&lt;메타휴먼 액터의 립싱크 블루프린트&gt;


마치며..

TTS 플러그인과 립싱크 플러그인을 따로 사용했지만 메타휴먼 액터의 블루프린트 구조는 간단하다.

TTS 변환 -> USoundWave 변환 -> Viseme값 연결 및 업데이트 -> 음성 재생.


솔직히 가장 손 많이 가고 귀찮은 작업은 Viseme 값과 적절한 커브값의 매칭이었다.
특히 영어 Viseme 값을 한국어 발음 모션으로 만들어야 한다는 점이 가장 어려웠다.
때문에 직접 Viseme 값을 탐색 및 생성할 수 있는 방법도 필요하겠다.