본문 바로가기

GameDevelopmentDiary/UnrealDiary

립싱크 - (메타휴먼, 리얼루전)

테스트1 - 립싱크 맛보기 테스트

 

  • 매개변수: Text와 음성파일
  • 표현방식: 블랜드 스페이스(BS)
  • 사용 모델: 리얼루전 캐릭터
  • 구현방법:
    더보기
    1. BS 생성. (기본 입모양 +  세로, 가로로 열린 각각의 입모양)
    2. Text를 Letter 객체들로 분리. Letter 객체는 초.중.종성 숫자 파라미터들을 가졌다. (0.0~1.0)
    3. 음성 전체 길이는 Letter 객체들이 나눠가진다. (공백 Letter 포함되며, 시간은 일반 자,모음보다 짧다.)
    3-1. Letter 내의 시간 길이는 초.중.종시간만큼 다시 나뉜다.
    4. 음성파일 실행.
    5. 음성 재생동안 Letter들을 이용해서 BS로 연결 된 변수값들을 순차적으로 변형.  
    5-1. 처음과 끝 Letter 경우 초성, 종성은 무조건 0.
    5-2. 현재 중성값에서 다음 중성값으로 BS 연결 변수값이 부드럽게 변형됨. (곡선그래프)
    5-3. 종성 또는 다음 Letter 초성이 비음처럼 입을 다문 경우는 초성과 종성값 사용.
  • 장점 : 빠른 구현.
  • 단점
    1. 음성파일의 재생에 딜레이가 생기면 시작부터 입모양과 소리의 동기화가 틀어진다.
    2. 음성파일은 글자마다 할당되는 시간이 다르므로 동기화가 틀어지는 경우가 많다. (특히 노래) 

---

 

테스트2 - Plugin 활용

  • 매개변수: Text
  • 표현방식: 애니메이션 인스턴스 내 Curve 값 변경, 오큘러스 립싱크
  • 사용 모델: 메타휴먼, 리얼루전 캐릭터
  • 사용 플러스인: TTS 플러그인, 오큘러스 플러그인(OVRLipsync 플러그인은 마켓플레이스에 없음.)
  • 구현방법:
    더보기
    1. BS 생성. (기본 입모양 +  세로, 가로로 열린 각각의 입모양)
    2. Text를 Letter 객체들로 분리. Letter 객체는 초.중.종성 숫자 파라미터들을 가졌다. (0.0~1.0)
    3. 음성 전체 길이는 Letter 객체들이 나눠가진다. (공백 Letter 포함되며, 시간은 일반 자,모음보다 짧다.)
    3-1. Letter 내의 시간 길이는 초.중.종시간만큼 다시 나뉜다.
    4. 음성파일 실행.
    5. 음성 재생동안 Letter들을 이용해서 BS로 연결 된 변수값들을 순차적으로 변형.  
    5-1. 처음과 끝 Letter 경우 초성, 종성은 무조건 0.
    5-2. 현재 중성값에서 다음 중성값으로 BS 연결 변수값이 부드럽게 변형됨. (곡선그래프)
    5-3. 종성 또는 다음 Letter 초성이 비음처럼 입을 다문 경우는 초성과 종성값 사용.
  • 장점: Wave값을 사용해서 입모양과 소리의 동기화가 비교적 자연스럽다.
  • 단점:
    1. TTS 이후 음성값이 인코딩 되어야하는데, 플러그인마다 매개변수 값이 다를 경우 소리가 튄다.
    해당 지식이 필요하며 쉽지 않다.
    2. 사용 가능한 발음별 변수값이 영어 기준이라 한글 기준보다 맞는 애니메이션 프로퍼티(커브) 찾기 힘들다.

  • 형 변환 코드 (바이너리 -> USoundWave):
void UTTSComponentWithMindsLab::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	TArray < uint8 > rawFile;
	SerializeWaveFile(rawFile, Response->GetContent().GetData(), Response->GetContent().Num(), 1, 44100);

	FWaveModInfo WaveInfo;
	if (WaveInfo.ReadWaveInfo(rawFile.GetData(), rawFile.Num())) {

		USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());
		if (!sw) {
			UE_LOG(LogTemp, Warning, TEXT("Null Sound Wave"));
			return;
		}

		int32 DurationDiv = *WaveInfo.pChannels * *WaveInfo.pBitsPerSample * *WaveInfo.pSamplesPerSec;
		if (DurationDiv <= 0)
		{
			UE_LOG(LogTemp, Warning, TEXT("Null Too small than DurationDiv"));
			return;
		}

		sw->Duration = *WaveInfo.pWaveDataSize * 16.0f / DurationDiv;
		sw->SetSampleRate(*WaveInfo.pSamplesPerSec/2); // 프로퍼티가 클 수록 소리가 느려짐.
		sw->NumChannels = *WaveInfo.pChannels;
		sw->RawPCMDataSize = WaveInfo.SampleDataSize;
		sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
		FMemory::Memmove(sw->RawPCMData, rawFile.GetData(), rawFile.Num());
		sw->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
		sw->InvalidateCompressedData();

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

		LastSoundWave = sw;
		SucceedSoundSetting = true;

		if (bIsAutoPlay) UGameplayStatics::PlaySound2D(this, sw);

		if(TTSEvent.IsBound())	TTSEvent.Broadcast(sw);
	}
}