멈추지 않고 끈질기게
[Unreal] 최종 프로젝트 14일차 - wav 파일 통신 바꾸기 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 청년취업사관학교 교육 과정의 최종 프로젝트에 관한 내용을 포함하고 있습니다.
※ 해당 포스팅은 Unreal 5.4.1 버전을 기준으로 작성되었습니다.
0. 서론
어제 한 중간발표는 시연플레이 연습을 좀 해놔서 나름대로 괜찮은 평가를 받으며 마무리할 수 있었습니다. 그러나 기쁨도 잠시, 점점 늦어지는 tts 응답에 대해 AI 강사님과 얘기를 나눈 결과 서버를 별도로 두어야 할 거 같다는 피드백을 받았습니다. 이전에 잠시 시도했다가 포기했던 wav 파일의 바이트 배열 통신을 다시 구현해야 하는 상황이 되었습니다.
1. wav 파일 주고 받기
우선 복잡한 과정을 빼고, 단순하게 바이트 배열로 wav 파일을 웹서버와 주고 받는 부분만 먼저 구현해보기로 했습니다. 우선 웹서버에서 받아온 바이트 배열을 wav 파일로 뽑는 부분을 먼저 해봤는데, 이부분은 다행히 큰 막힘없이 성공하였습니다.
// fastapi 웹서버
@app.get("/get-wav")
async def get_wav():
with open(wav_path, "rb") as wav_file:
wav_data = wav_file.read()
return Response(content=wav_data, media_type="audio/wav")
// 언리얼
void AServerTestActor::GetWavFile()
{
const FString& FullURL = TEXT("http://127.0.0.1:8000/get-wav");
// HTTP Request
TSharedRef<IHttpRequest> HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->SetVerb(TEXT("GET"));
HttpRequest->SetURL(FullURL);
HttpRequest->OnProcessRequestComplete().BindUObject(this, &AServerTestActor::GetWavFileComplete);
HttpRequest->ProcessRequest();
}
void AServerTestActor::GetWavFileComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bConnectedSuccessfully)
{
if (bConnectedSuccessfully)
{
const TArray<uint8>& Data = Response->GetContent();
FString FilePath = FPaths::ProjectContentDir() + "ReceivedFile.wav";
FFileHelper::SaveArrayToFile(Data, *FilePath);
UE_LOG(LogTemp, Warning, TEXT("WAV file saved to %s"), *FilePath);
}
else
{
if (Request->GetStatus() == EHttpRequestStatus::Succeeded)
{
UE_LOG(LogTemp, Warning, TEXT("Response Failed...%d"), Response->GetResponseCode());
}
}
}
여기까진 좋았는데, 언리얼에서 웹서버로 wav 파일을 바이트 배열 형태로 보내는 작업이 계속 422 unprocessable entity 에러를 발생하여 애를 먹었습니다. 언리얼 쪽에서 http 통신으로 파일을 바이트 배열 형태로 보내는 통신은 처음이라, 레퍼런스로 찾은 코드에서 문제가 되는 부분을 찾는 작업이 상당히 어려웠습니다.
void AServerTestActor::SendWavFile(const FString& FilePath)
{
FHttpModule* Http = &FHttpModule::Get();
if (!Http) { return; }
TArray<uint8> FileContent;
if (!FFileHelper::LoadFileToArray(FileContent, *FilePath))
{
UE_LOG(LogTemp, Error, TEXT("Failed to load file %s"), *FilePath);
return;
}
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
// Set Boundary
FString Boundary = TEXT("BoundaryString123456789012345678901234");
FString BeginBoundary = TEXT("--") + Boundary + TEXT("\r\n");
FString EndBoundary = TEXT("\r\n--") + Boundary + TEXT("--\r\n");
FString Header = BeginBoundary;
Header.Append(TEXT("Content-Disposition: form-data; name=\"UploadFile\"; filename=\"")); // 이슈 원인
Header.Append(FPaths::GetCleanFilename(FilePath));
Header.Append(TEXT("\"\r\n"));
Header.Append(TEXT("Content-Type: audio/wav\r\n\r\n"));
TArray<uint8> Payload;
Payload.Append(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*Header)), Header.Len());
Payload.Append(FileContent);
Payload.Append(reinterpret_cast<uint8*>(TCHAR_TO_UTF8(*EndBoundary)), EndBoundary.Len());
// Server URL
Request->SetURL("http://127.0.0.1:8000/upload-wav");
Request->SetVerb("POST");
Request->SetHeader("Content-Type", FString("multipart/form-data; boundary=") + Boundary);
Request->SetContent(Payload);
Request->OnProcessRequestComplete().BindUObject(this, &AServerTestActor::SendWavFileComplete);
Request->ProcessRequest();
}
문제는 헤더에 정보를 입력하는 부분에서 발생했습니다. 상기 코드를 보면 헤더에 name=\"file\" 내용을 추가하는 부분이 있는데, 해당 부분을 수정해 버려서 file 필드값을 찾지 못하여 발생하는 문제였습니다. 어쩌다가 저 부분을 건드린건지 모르겠는데, 해당 부분이 임의로 수정하면 안된다는 점 하나는 확실하게 기억하게 되었습니다.
2. tts 결과 언리얼에서 출력하기
원래 생각은 tts 결과물을 파일로 저장하지 않고바이트 배열 형태로 전달하려 했으나, 현재 프로젝트에 적용되어 있는 OpenVoice 모델에서 변환을 거치고 나면 아예 wav 파일로 저장하기 때문에 불가능하다는 것을 깨달았습니다. 다만 해당 방식이 가능한지만 확인하기 위해 tts-1 모델을 사용해 시도해 보았었는데, 해당 과정에서 처음으로 405 Method Not Allowed 에러를 겪었습니다.
// 405 에러
@app.post("/tts-post")
async def tts_post(data:NPCData):
tts_data = tts(data.npc_name, data.npc_chat)
return Response(content=tts_data, media_type="audio/wav")
tts() 함수는 챗봇 답변을 받은 뒤, tts-1 모델로 음성 파일을 생성해서 바이트 배열 형태로 반환하는 함수입니다. 위와 같은 방식으로 NPC 이름과 채팅 내용을 보내고, 바로 답변을 리턴받아서 사용하고 싶었지만 Response 클래스를 POST 방식에서 사용하는 것은 불가능했습니다. 결국 기존에 하던대로 GET 엔드포인트를 별도로 만들어 해결하였습니다.
// 405 에러 해결
@app.post("/tts-post")
async def tts_post(data:NPCData):
tts(data.npc_name, data.npc_chat)
return "tts complete"
@app.get("/tts-get")
async def tts_get():
return Response(content=tts_data, media_type="audio/wav")
이렇게 해서 일단 파일 경로가 아닌 바이트 배열 형태로 통신하는 틀은 갖춰졌고, 주말동안 기존의 웹서버 및 언리얼쪽 통신 관련 코드를 수정하는 데 매진해야 할 듯 합니다.
'포트폴리오' 카테고리의 다른 글
[Unreal] 최종 프로젝트 17일차 - 감정표현 UI 수정 / 채팅 개선 / BT 이슈 수정 (1) | 2024.06.07 |
---|---|
[Unreal] 최종 프로젝트 16일차 - NPC 감정표현 추가 / 선물 주기 로직 추가 (0) | 2024.06.04 |
[Unreal] 최종 프로젝트 13일차 - NPC 실전 배치 및 테스트 / 마이크 입력 중 출력 방지 (0) | 2024.05.29 |
[Unreal] 최종 프로젝트 12일차 - 식물 성장 기능 머지 / NPC 행동 패턴 고도화 (0) | 2024.05.28 |
[Unreal] 최종 프로젝트 11일차 - STT & 채팅 기능 머지 / NPC 인사 구현 (0) | 2024.05.27 |