음성 합성을 위한 과정
사용자의 요청에 따라 음성 파일을 합성하는 전반적인 과정은 다음과 같다.
1) 클라이언트 요청
2) 해당 음성 모델로부터 Systhesizer object initialize
synthesizer = Synthesizer(
tts_checkpoint = f"{tts_model_file_path}",
tts_config_path = f"{tts_config_path}"
)
3) 2에서 만든 Synthesizer object로부터 tts 함수 호출(wav 파일 생성)
symbol = synthesizer.tts_config.characters.characters # symbol set
text = normalize_text(text, symbol) # text normalize
wav = synthesizer.tts(text, None, None) # conver text to speech
synthesizer.save_wav(wav, wav_path) # write wav file
4) 생성된 wav 파일을 gcs(google cloud storage) bucket에 저장
def upload_wav_to_bucket(wav_path, email, uuid):
storage_client = storage.Client()
bucket_name = BUCKET_NAME
source_file_name = wav_path
destination_blob_name = f'{email}/{uuid}.wav'
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(destination_blob_name)
blob.upload_from_filename(source_file_name)
문제는 너무 느리다는 것..
음성 하나 합성하는데 걸리는 시간이 너무 느리다는 것이 계속 걸렸다.
2번과 3번 과정이 가장 핵심적인 부분이었고, 각각 과정(특히, 2번)들에서 많은 시간이 소요되었다.
애초에 coqui-tts
에서 제공하는 라이브러리를 사용하여 음성 합성을 진행하고 있기 때문에,
위의 과정 중 3번에 해당하는 text를 기반으로 wav 파일을 합성하는 데 걸리는 시간은 건드리기 힘들었다.
따라서, 작업 요청마다 Synthesizer object가 initialize되는 시간만 날려버려도, 시간을 상당히 많이 줄일 수 있을 것이라 판단했고, object를 미리 만들고, 별도의 storage에서 불러와서 사용하도록 수정하였다.
object pool 패턴
실제로 동작 시간을 줄이기 위해 매 요청마다 object를 생성하지 않고, 미리 만들어놓은 object들을 다시 사용하는 object pool 패턴이라는 것이 있다.
db 연결에 사용되는 connection pool
과 쓰레드를 담고 있는 thread pool
이 그 예시이다.
이 프로젝트에서는 pool까진 이용하지 않지만, 위의 패턴에서 아이디어를 얻어왔다. 매 요청마다 소요되는 object의 생성 시간을 줄이기 위해 사용자 개개인의 음성 정보를 담은 synthesizer object를 미리 만들어서 별도의 object storage로부터 불러와 사용하는 것으로 음성 합성 시간을 단축시키고자 하였다.
정리하자면,
- 기존에 학습 시켜둔 음성 모델을 기반으로
1) 사용자 개인의 녹음 음성 파일을 받아서 학습 시켜둔 모델로 synthesizer object를 initialize.
2) synthesizer object를 object storeage(gcs)에 저장 - 음성 합성 요청 시
1) 클라이언트 요청
2) object storage에 접근하여 미리 저장해둔 synthesizer objcet를 불러옴
3) 불러온 object로 TTS 수행
class의 instance를 object storage에 저장해보자.
인터넷에 보통 파일 형태의 data만 저장하는 예제들이 많지만, 굳이 파일 형태로 국한될 필요는 없다.
object storage는 말 그대로 객체 저장소이므로, memory에서 바로 byte 형태로 data를 업로드하는 것도 가능하다.
python의 pickle 모듈을 사용하여 객체를 직렬화하여 저장하는 방식을 사용했다.
import pickle
# synthesizer initialize
synthesizer = Synthesizer(...) # 상세 코드 생략
# synthesizer 객체를 pickle로 serialize하여 storage에 저장
upload_model_to_bucket(email, pickle.dumps(synthesizer))
# 만들어진 음성 model을 bucket에 업로드하는 메소드
def upload_model_to_bucket(email, model):
destination_blob_name = f'{email}/{email}_best_model'
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)
blob = bucket.blob(destination_blob_name)
blob.upload_from_string(model)
수행 결과
같은 길이의 문장을 기준으로 적용하기 전과 후를 비교해보았다.
리팩토링을 통해 해당 작업을 21초에서 8초로 줄일 수 있었다!
Reference
1) https://cloud.google.com/storage/docs/uploading-objects-from-memory?hl=ko [gcs document]
'Project > Voice Pocket' 카테고리의 다른 글
[Voice Pocket] 강한 결합을 가진 로직을 느슨하게 풀어보자(feat. spring event) (0) | 2024.03.05 |
---|---|
[Voice Pocket] 더블 클릭 이슈 해결기 (0) | 2024.03.01 |
[Voice Pocket] Message Queue 뒷 단으로 보낸 작업의 결과를 확인하는 법 (0) | 2024.03.01 |
[Voice Pocket] spring boot에서 celery 활용하기 (1) | 2024.03.01 |
[Voice Pocket] 프로젝트 소개 (0) | 2024.02.29 |