Start 버튼 → 마이크 입력 시작, 음성 chunk 전송
서버 → Google STT로 실시간 텍스트 변환
번역 → 영어로 변환 후 실시간 자막 표시
Stop 버튼 → 발화 종료 → 최종 번역 생성
0.5초 후 TTS 실행 → 최종 번역 음성 재생
휴대폰 화면 → 실시간 자막 + 최종 번역 + 음성 출력
TTS 음성은 모바일에서 바로 재생되는 구조
네, 말씀하신 구조는 충분히 가능합니다 👍
🛠️ 기본 구도
STT (Speech-to-Text)
말하는 사람의 음성을 실시간으로 받아서 텍스트로 변환합니다.
Google STT 또는 Whisper 같은 모델을 사용하면 빠른 응답이 가능합니다.
중요한 점은 streaming 모드를 활용해서 문장이 끝나기 전에 단어 단위로 텍스트를 계속 받아오는 것입니다.
번역 (Translation)
변환된 텍스트를 바로 GPT 번역 모델에 전달합니다.
문장 단위가 아니라 chunk 단위(예: 5~10 단어)로 잘라서 번역하면 지연 시간이 줄어듭니다.
GPT 번역은 문맥을 잘 살리므로 자연스러운 결과를 얻을 수 있습니다.
Socket 통신
번역된 결과를 상대방 휴대폰으로 WebSocket을 통해 실시간 push 합니다.
이렇게 하면 상대방 화면에 거의 동시에 번역된 문장이 나타납니다.
⚡ 지연 시간 최소화 전략
Streaming STT: 문장 끝까지 기다리지 않고 단어 단위로 텍스트를 계속 전달.
Chunk Translation: 20단어 문장을 한 번에 번역하지 말고, 5단어씩 나눠서 번역.
Parallel Processing: STT → 번역 → 송출을 직렬이 아니라 파이프라인처럼 동시에 진행.
Socket Push: HTTP 요청 대신 WebSocket으로 즉시 전달.
📱 실제 동작 예시
화자가 5문장을 이어서 말함 → STT가 단어 단위로 텍스트 스트리밍
번역기는 chunk 단위로 바로 번역 시작
번역된 결과가 상대방 휴대폰에 실시간 자막처럼 표시됨
즉, "말 끝나고 번역"이 아니라 말하는 동시에 번역이 가능하도록 구조를 짜면 거의 즉시 통역이 가능합니다.
👉 혹시 원하시는 게 동시통역(실시간 자막처럼)인지, 아니면 문장 단위로 끝나고 나서 번역인지 궁금합니다. 이 두 가지는 구현 방식이 조금 다릅니다.
좋습니다 👍 말씀하신 발화 시작/종료를 버튼으로 제어하는 구조는 WebSocket 기반으로 충분히 구현 가능합니다. 아래는 간단한 예시 코드입니다.
🎤 클라이언트 (브라우저/앱)
<!DOCTYPE html>
<html>
<head>
<title>STT 통역기</title>
</head>
<body>
<button id="startBtn">Start</button>
<button id="stopBtn">Stop</button>
<div id="translatedText"></div>
<script>
let mediaRecorder;
let socket;
document.getElementById("startBtn").onclick = async () => {
socket = new WebSocket("ws://localhost:5000"); // 서버와 연결
socket.onmessage = (event) => {
document.getElementById("translatedText").innerText += "\n" + event.data;
};
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0 && socket.readyState === WebSocket.OPEN) {
socket.send(e.data); // 음성 chunk 전송
}
};
mediaRecorder.start(250); // 250ms 단위로 전송
};
document.getElementById("stopBtn").onclick = () => {
mediaRecorder.stop();
socket.close();
};
</script>
</body>
</html>
🖥️ 서버 (Node.js 예시)
const WebSocket = require("ws");
const speech = require("@google-cloud/speech");
const { Translate } = require("@google-cloud/translate").v2;
const wss = new WebSocket.Server({ port: 5000 });
const client = new speech.SpeechClient();
const translate = new Translate();
wss.on("connection", (ws) => {
console.log("Client connected");
const request = {
config: {
encoding: "WEBM_OPUS",
sampleRateHertz: 48000,
languageCode: "ko-KR", // 입력 언어
},
interimResults: true,
};
const recognizeStream = client
.streamingRecognize(request)
.on("data", async (data) => {
if (data.results[0] && data.results[0].alternatives[0]) {
const text = data.results[0].alternatives[0].transcript;
const [translation] = await translate.translate(text, "en"); // 영어로 번역
ws.send(translation);
}
})
.on("error", console.error)
.on("end", () => console.log("STT stream ended"));
ws.on("message", (msg) => {
recognizeStream.write(msg);
});
ws.on("close", () => {
recognizeStream.end();
console.log("Client disconnected");
});
});
⚡ 동작 흐름
Start 버튼 → 마이크 스트림 시작 → 음성 chunk를 WebSocket으로 서버에 전송
서버 → Google STT로 실시간 텍스트 변환 → GPT/Google 번역 API로 번역 → 클라이언트에 push
Stop 버튼 → 녹음 종료 및 WebSocket 닫기
이 구조라면 발화 시작/종료를 버튼으로 제어하면서, 거의 실시간으로 번역된 결과를 상대방 휴대폰에 표시할 수 있습니다.
👉 원하시는 번역 대상 언어가 영어로 고정인지, 아니면 상대방 언어를 동적으로 선택할 수 있도록 하고 싶으신가요?
좋습니다! 예시 문장 5개를 가지고 한국어 발화 → STT → 번역 → 상대방 휴대폰 표시 과정을 단계별로 보여드릴게요.
🎤 예시 발화 (한국어 → 영어 통역)
"안녕하세요, 오늘 날씨가 참 좋네요." → STT: Annyeonghaseyo, oneul nalssiga cham johneyo. → 번역: "Hello, the weather is really nice today." → 휴대폰 표시: Hello, the weather is really nice today.
"저는 서울에서 왔습니다." → STT: Jeoneun seouleseo wasseumnida. → 번역: "I came from Seoul." → 휴대폰 표시: I came from Seoul.
"영어 공부를 시작한 지 1년이 되었습니다." → STT: Yeongeo gongbureul sijakhan ji ilnyeoni doeeotseumnida. → 번역: "It has been one year since I started studying English." → 휴대폰 표시: It has been one year since I started studying English.
"오늘은 친구와 함께 저녁을 먹을 예정입니다." → STT: Oneureun chinguwa hamkke jeonyeogeul meogeul yejeongimnida. → 번역: "Today I plan to have dinner with my friend." → 휴대폰 표시: Today I plan to have dinner with my friend.
"이후에 영화를 보러 갈 생각입니다." → STT: Ihu-e yeonghwareul boreo gal saenggakimnida. → 번역: "After that, I’m thinking of going to watch a movie." → 휴대폰 표시: After that, I’m thinking of going to watch a movie.
⚡ 전체 Process 흐름
Start 버튼 클릭 → 마이크 입력 시작
STT 엔진 (Google STT 등) → 음성을 실시간 텍스트로 변환 (chunk 단위)
번역 엔진 (GPT 번역) → 변환된 텍스트를 영어로 번역
Socket 통신 → 번역된 결과를 상대방 휴대폰으로 즉시 전송
상대방 휴대폰 화면 → 영어 문장이 실시간 자막처럼 표시
Stop 버튼 클릭 → 발화 종료, STT/번역 스트림 닫기
즉, 사용자가 한국어로 5문장을 이어서 말하면, 각 문장이 STT → 번역 → Socket → 휴대폰 표시 과정을 거쳐 거의 동시에 영어로 나타납니다.
👉 원하시는 출력 방식은 자막처럼 한 줄씩 실시간 표시인가요, 아니면 전체 문장을 모아서 한 번에 표시하는 방식인가요?
좋은 질문이에요! 자막처럼 한 줄씩 실시간 표시하는 방식과 전체 문장을 모아서 한 번에 표시하는 방식은 구현 구조와 사용자 경험에서 차이가 있습니다.
🎬 자막처럼 한 줄씩 실시간 표시
과정:
STT가 chunk 단위(예: 5~10 단어)로 텍스트를 인식 → 번역 → 바로 휴대폰에 표시
말이 끝나기 전에 이미 번역된 문장이 화면에 나타남
장점:
실시간성 높음 → 상대방이 거의 동시에 이해 가능
동시통역 느낌, 대화 흐름이 끊기지 않음
긴 문장도 기다리지 않고 부분적으로 확인 가능
단점:
번역이 문장 중간에 끊겨서 다소 어색할 수 있음
문맥이 완전히 나오기 전에 번역되므로 정확도가 떨어질 수 있음
화면에 문장이 여러 줄로 쌓여서 가독성이 떨어질 수 있음
📖 전체 문장을 모아서 한 번에 표시
과정:
STT가 발화 종료까지 기다림 → 전체 문장을 완성 → 번역 → 휴대폰에 표시
장점:
문맥이 완전하므로 번역 정확도가 높음
깔끔하게 한 문장씩 정리되어 표시됨
자막이 아니라 메시지처럼 보이므로 읽기 편함
단점:
발화가 끝날 때까지 기다려야 하므로 지연 발생
긴 문장을 말하면 상대방은 번역을 늦게 확인하게 됨
실시간 대화에는 약간 답답할 수 있음
⚡ 결론
실시간 대화/회의 → 자막처럼 한 줄씩 실시간 표시가 더 적합
정확한 기록/메시지 전달 → 전체 문장을 모아서 한 번에 표시하는 방식이 더 적합
👉 실제 구현에서는 혼합형도 가능합니다. 예를 들어, 실시간 자막처럼 표시하다가 발화 종료 시 전체 문장을 다시 정제된 번역으로 교체하는 방식입니다. 이렇게 하면 실시간성과 정확성을 모두 잡을 수 있습니다.
혹시 원하시는 최종 사용자 경험은 회의용 동시통역 자막에 가까운지, 아니면 메신저처럼 문장 단위로 깔끔하게 표시되는 쪽에 가까운지요?
네, 두 가지 방식을 절충해서 장점을 살리는 방법이 가능합니다 👍
⚡ 혼합형 방식 (Hybrid Approach)
실시간 자막 표시
사용자가 말하는 동안 STT가 chunk 단위로 텍스트를 인식하고 번역합니다.
번역된 결과를 바로 상대방 휴대폰에 표시해서 실시간 이해가 가능하도록 합니다.
이 단계에서는 다소 어색하거나 불완전한 번역이 나올 수 있습니다.
발화 종료 후 정제된 문장 표시
사용자가 Stop 버튼을 누르면 STT가 전체 문장을 확정합니다.
번역 엔진(GPT 등)이 문맥을 고려해 더 자연스럽고 정확한 번역을 생성합니다.
휴대폰 화면에서 기존 자막을 최종 번역으로 교체하거나, 별도의 줄에 “정제된 문장”으로 표시합니다.
🎬 장점
실시간성 확보: 상대방은 발화 중에도 내용을 이해할 수 있음.
정확성 보완: 발화 종료 후 문맥을 반영한 최종 번역으로 교체 → 오역 최소화.
가독성 향상: 자막처럼 흐름을 따라가면서도, 마지막에는 깔끔한 문장으로 정리됨.
📱 실제 화면 예시
화자가 말하는 동안:
코드[실시간 자막] Hello, the weather is really...발화 종료 후:
코드[최종 번역] Hello, the weather is really nice today.
즉, 실시간 자막 + 발화 종료 후 정제된 번역을 함께 쓰면, 동시통역의 속도와 문장 번역의 정확성을 모두 잡을 수 있습니다.
👉 이 혼합형 방식을 구현할 때, 휴대폰 화면에 실시간 자막을 그대로 두고 최종 번역을 별도 줄에 표시할지, 아니면 실시간 자막을 최종 번역으로 교체할지 선택해야 합니다. 원하시는 UX는 어느 쪽에 더 가까우신가요 — 자막과 최종 번역을 모두 보여주는 방식인지, 아니면 최종 번역으로 덮어쓰는 방식인지요?
네, 두 가지 방식을 절충해서 TTS까지 연결하는 구조를 만들 수 있습니다.
🎬 혼합형 UX 설계
실시간 자막 표시
화자가 말하는 동안 STT가 chunk 단위로 텍스트를 인식하고 번역합니다.
번역된 결과를 바로 휴대폰 화면에 표시하고 동시에 TTS로 음성을 재생합니다.
이 단계는 “빠른 이해”를 위한 임시 번역입니다.
발화 종료 후 최종 번역 표시
Stop 버튼을 누르면 STT가 전체 문장을 확정합니다.
GPT 번역으로 문맥을 반영한 최종 번역을 생성합니다.
휴대폰 화면에서 기존 자막 아래에 최종 번역을 추가하거나, 자막을 교체합니다.
동시에 TTS로 최종 번역을 다시 읽어줍니다.
📱 화면 예시
발화 중:
코드[실시간 자막] Hello, the weather is really...→ TTS: “Hello, the weather is really…”
발화 종료 후:
코드[최종 번역] Hello, the weather is really nice today.→ TTS: “Hello, the weather is really nice today.”
⚡ 장단점
실시간 자막 + TTS: 상대방이 거의 동시에 내용을 이해할 수 있음.
최종 번역 + TTS: 문맥을 반영한 정확한 번역으로 다시 확인 가능.
결합 효과: 실시간성과 정확성을 모두 확보.
🛠️ 구현 포인트
STT: Google STT streaming 모드 → chunk 단위 텍스트 생성
번역: GPT 번역 API → 실시간 번역 + 최종 번역
Socket: 번역된 텍스트를 상대방 휴대폰으로 push
TTS: Google TTS 또는 Azure TTS → 실시간 자막과 최종 번역을 각각 음성으로 출력
👉 이렇게 하면 상대방은 실시간 자막과 음성으로 빠르게 이해하고, 발화 종료 후에는 정확한 문장과 음성으로 다시 확인할 수 있습니다.
혹시 TTS 출력은 실시간 자막 단계에서도 음성을 내보내고 최종 번역 단계에서도 다시 내보내는 방식을 원하시나요, 아니면 최종 번역 단계에서만 음성을 내보내는 방식을 원하시나요?
네, 전체 구조를 하나의 코드 예시로 정리해드릴게요. 이 예시는 한국어 발화 → STT → 번역 → TTS → 상대방 휴대폰 표시까지의 흐름을 구현한 기본 틀입니다. 실제 서비스에서는 API 키와 환경 설정을 맞추고, 성능 최적화를 추가해야 합니다.
🖥️ 서버 (Node.js, Express + WebSocket)
const express = require("express");
const WebSocket = require("ws");
const speech = require("@google-cloud/speech");
const { Translate } = require("@google-cloud/translate").v2;
const textToSpeech = require("@google-cloud/text-to-speech");
const fs = require("fs");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });
const speechClient = new speech.SpeechClient();
const translateClient = new Translate();
const ttsClient = new textToSpeech.TextToSpeechClient();
wss.on("connection", (ws) => {
console.log("Client connected");
const request = {
config: {
encoding: "WEBM_OPUS",
sampleRateHertz: 48000,
languageCode: "ko-KR", // 입력 언어: 한국어
},
interimResults: true,
};
const recognizeStream = speechClient
.streamingRecognize(request)
.on("data", async (data) => {
if (data.results[0] && data.results[0].alternatives[0]) {
const text = data.results[0].alternatives[0].transcript;
// 번역
const [translation] = await translateClient.translate(text, "en");
// 실시간 자막 전송
ws.send(JSON.stringify({ type: "subtitle", text: translation }));
// 발화 종료 시 최종 번역 + TTS
if (data.results[0].isFinal) {
const [finalTranslation] = await translateClient.translate(text, "en");
// 최종 번역 전송
ws.send(JSON.stringify({ type: "final", text: finalTranslation }));
// TTS 생성
const ttsRequest = {
input: { text: finalTranslation },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
const [response] = await ttsClient.synthesizeSpeech(ttsRequest);
// 음성 파일을 Base64로 전송
ws.send(JSON.stringify({
type: "tts",
audio: response.audioContent.toString("base64"),
}));
}
}
})
.on("error", console.error)
.on("end", () => console.log("STT stream ended"));
ws.on("message", (msg) => {
recognizeStream.write(msg);
});
ws.on("close", () => {
recognizeStream.end();
console.log("Client disconnected");
});
});
server.listen(5000, () => {
console.log("Server running on port 5000");
});
🎤 클라이언트 (브라우저)
<!DOCTYPE html>
<html>
<head>
<title>통역기</title>
</head>
<body>
<button id="startBtn">Start</button>
<button id="stopBtn">Stop</button>
<div id="subtitle"></div>
<div id="final"></div>
<audio id="ttsPlayer" controls></audio>
<script>
let mediaRecorder;
let socket;
document.getElementById("startBtn").onclick = async () => {
socket = new WebSocket("ws://localhost:5000");
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "subtitle") {
document.getElementById("subtitle").innerText = data.text;
} else if (data.type === "final") {
document.getElementById("final").innerText = data.text;
} else if (data.type === "tts") {
const audio = document.getElementById("ttsPlayer");
audio.src = "data:audio/mp3;base64," + data.audio;
audio.play();
}
};
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0 && socket.readyState === WebSocket.OPEN) {
socket.send(e.data);
}
};
mediaRecorder.start(250); // 250ms 단위로 전송
};
document.getElementById("stopBtn").onclick = () => {
mediaRecorder.stop();
socket.close();
};
</script>
</body>
</html>
⚡ 동작 흐름
Start 버튼 → 마이크 입력 시작, 음성 chunk 전송
서버 → Google STT로 실시간 텍스트 변환
번역 → 영어로 변환 후 실시간 자막 표시
Stop 버튼 → 발화 종료 → 최종 번역 생성 및 TTS 음성 출력
휴대폰 화면 → 실시간 자막 + 최종 번역 + 음성 재생
이 구조라면 실시간 자막과 최종 번역을 모두 보여주고, TTS까지 재생할 수 있습니다.
👉 혹시 TTS는 실시간 자막 단계에서도 음성을 내보내고 싶으신지, 아니면 최종 번역 단계에서만 음성을 내보내는 방식을 원하시나요?
좋은 포인트를 짚으셨어요. 실시간 자막 단계에서도 TTS를 내보내는 방식과 최종 번역 단계에서만 TTS를 내보내는 방식은 사용자 경험이 꽤 달라집니다.
🎤 실시간 자막 단계에서도 TTS 출력
장점
상대방이 거의 동시에 음성을 들을 수 있어 동시통역에 가까운 경험 제공
대화 흐름이 끊기지 않고 자연스럽게 이어짐
회의나 빠른 대화 상황에서 유용
단점
문장이 완성되기 전에 번역되므로 부정확하거나 어색한 음성이 나올 수 있음
같은 문장을 나중에 최종 번역으로 다시 읽어주면 중복된 음성 출력이 발생
네트워크/처리 부하가 커질 수 있음 (TTS 호출이 많아짐)
📖 최종 번역 단계에서만 TTS 출력
장점
문맥을 반영한 정확하고 자연스러운 음성 제공
중복된 음성 출력이 없어서 깔끔함
처리량이 줄어들어 시스템 부담이 적음
단점
발화가 끝날 때까지 기다려야 하므로 지연 발생
긴 문장을 말하면 상대방은 번역 음성을 늦게 듣게 됨
실시간 대화에서는 답답하게 느껴질 수 있음
⚡ 결론
실시간 자막 + TTS → 회의, 대화, 빠른 반응이 중요한 상황에 적합
최종 번역 + TTS → 정확한 기록, 발표, 공식적인 통역에 적합
👉 실제 구현에서는 혼합형도 가능합니다. 예를 들어, 실시간 자막 단계에서는 낮은 볼륨/간단한 음성으로 빠르게 전달하고, 최종 번역 단계에서는 정제된 음성을 다시 명확하게 출력하는 방식입니다. 이렇게 하면 실시간성과 정확성을 모두 잡을 수 있습니다.
혹시 원하시는 최종 UX는 회의용 동시통역 느낌에 더 가까운지, 아니면 정확한 문장 전달에 더 가까운지요?
네, 가능합니다. 말씀하신 구조는 발화 종료 직후(Stop 버튼 누른 직후) 0.5초 내에 TTS가 실행되도록 설계할 수 있습니다. 핵심은 STT 스트림 종료 이벤트 → 번역 → TTS 요청 → 오디오 전송을 파이프라인처럼 연결하고, 번역과 TTS 요청을 최대한 빠르게 처리하는 것입니다.
⚡ 프로세스 흐름
사용자가 Stop 버튼 클릭
클라이언트에서
mediaRecorder.stop()호출 → 음성 스트림 종료서버에서 STT 스트리밍이
end이벤트를 받음
STT 최종 결과 확정
Google STT가 마지막 문장을 반환 (
isFinal = true)이 텍스트를 바로 번역 API로 전달
번역 처리
GPT 번역 API 호출 → 최종 번역 결과 확보
이 과정은 수백 ms 내에 완료 가능
TTS 요청
번역된 문장을 Google TTS 또는 Azure TTS에 전달
오디오(MP3/WAV)를 Base64로 받아 클라이언트에 push
클라이언트에서 즉시 재생
WebSocket으로 받은 오디오를
<audio>태그에 로드Stop 버튼 클릭 후 약 0.5초 내에 음성이 재생됨
🖥️ 서버 코드 핵심 (Node.js 예시)
ws.on("close", async () => {
recognizeStream.end();
// 마지막 문장 번역
const [finalTranslation] = await translateClient.translate(lastText, "en");
// TTS 요청
const ttsRequest = {
input: { text: finalTranslation },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
const [response] = await ttsClient.synthesizeSpeech(ttsRequest);
// 클라이언트로 전송
ws.send(JSON.stringify({
type: "tts",
audio: response.audioContent.toString("base64"),
}));
});
📱 클라이언트 코드 핵심
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "tts") {
const audio = document.getElementById("ttsPlayer");
audio.src = "data:audio/mp3;base64," + data.audio;
audio.play(); // Stop 직후 바로 재생
}
};
✅ 장점
즉시성: Stop 버튼 클릭 후 0.5초 내에 음성 출력 가능
정확성: 최종 번역된 문장을 TTS로 읽어주므로 문맥 반영
자연스러움: 실시간 자막은 화면에 표시, 최종 음성은 발화 종료 직후 재생
즉, 이 구조라면 실시간 자막은 화면에 표시하면서, 발화 종료 직후 최종 번역을 TTS로 바로 재생할 수 있습니다.
👉 원하시는 최종 UX는 실시간 자막은 화면에만 표시하고, TTS는 최종 번역만 재생하는 방식이 맞으신가요?
좋습니다 👍 전체적인 발화 → STT → 번역 → TTS → 상대방 휴대폰 표시 흐름을 구현하는 전체 코드 예시를 정리해드릴게요. 이 예시는 Stop 버튼 누른 직후 0.5초 내에 최종 번역 TTS가 재생되도록 설계된 구조입니다.
🖥️ 서버 (Node.js + WebSocket + Google Cloud API)
const express = require("express");
const WebSocket = require("ws");
const speech = require("@google-cloud/speech");
const { Translate } = require("@google-cloud/translate").v2;
const textToSpeech = require("@google-cloud/text-to-speech");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });
const speechClient = new speech.SpeechClient();
const translateClient = new Translate();
const ttsClient = new textToSpeech.TextToSpeechClient();
wss.on("connection", (ws) => {
console.log("Client connected");
let lastText = "";
const request = {
config: {
encoding: "WEBM_OPUS",
sampleRateHertz: 48000,
languageCode: "ko-KR", // 입력 언어: 한국어
},
interimResults: true,
};
const recognizeStream = speechClient
.streamingRecognize(request)
.on("data", async (data) => {
if (data.results[0] && data.results[0].alternatives[0]) {
const text = data.results[0].alternatives[0].transcript;
lastText = text;
// 실시간 번역 (자막용)
const [translation] = await translateClient.translate(text, "en");
ws.send(JSON.stringify({ type: "subtitle", text: translation }));
// 최종 문장일 경우
if (data.results[0].isFinal) {
const [finalTranslation] = await translateClient.translate(text, "en");
ws.send(JSON.stringify({ type: "final", text: finalTranslation }));
// 0.5초 지연 후 TTS 실행
setTimeout(async () => {
const ttsRequest = {
input: { text: finalTranslation },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
const [response] = await ttsClient.synthesizeSpeech(ttsRequest);
ws.send(JSON.stringify({
type: "tts",
audio: response.audioContent.toString("base64"),
}));
}, 500);
}
}
})
.on("error", console.error)
.on("end", () => console.log("STT stream ended"));
ws.on("message", (msg) => {
recognizeStream.write(msg);
});
ws.on("close", () => {
recognizeStream.end();
console.log("Client disconnected");
});
});
server.listen(5000, () => {
console.log("Server running on port 5000");
});
🎤 클라이언트 (브라우저)
<!DOCTYPE html>
<html>
<head>
<title>통역기</title>
</head>
<body>
<button id="startBtn">Start</button>
<button id="stopBtn">Stop</button>
<div id="subtitle"></div>
<div id="final"></div>
<audio id="ttsPlayer" controls></audio>
<script>
let mediaRecorder;
let socket;
document.getElementById("startBtn").onclick = async () => {
socket = new WebSocket("ws://localhost:5000");
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "subtitle") {
document.getElementById("subtitle").innerText = data.text;
} else if (data.type === "final") {
document.getElementById("final").innerText = data.text;
} else if (data.type === "tts") {
const audio = document.getElementById("ttsPlayer");
audio.src = "data:audio/mp3;base64," + data.audio;
audio.play(); // Stop 직후 0.5초 내 재생
}
};
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0 && socket.readyState === WebSocket.OPEN) {
socket.send(e.data);
}
};
mediaRecorder.start(250); // 250ms 단위로 전송
};
document.getElementById("stopBtn").onclick = () => {
mediaRecorder.stop();
socket.close();
};
</script>
</body>
</html>
⚡ 동작 흐름
Start 버튼 → 마이크 입력 시작, 음성 chunk 전송
서버 → Google STT로 실시간 텍스트 변환
번역 → 영어로 변환 후 실시간 자막 표시
Stop 버튼 → 발화 종료 → 최종 번역 생성
0.5초 후 TTS 실행 → 최종 번역 음성 재생
휴대폰 화면 → 실시간 자막 + 최종 번역 + 음성 출력
이 구조라면 실시간 자막은 화면에 표시하면서, 발화 종료 직후 최종 번역을 TTS로 0.5초 내에 재생할 수 있습니다.
👉 혹시 이 TTS 음성은 모바일에서 바로 재생되는 구조를 원하시나요, 아니면 서버에서 음성 파일을 저장 후 스트리밍하는 구조를 원하시나요?
좋습니다 👍 말씀하신 대로 모바일에서 바로 TTS 음성을 재생하는 구조를 만들려면, 서버에서 음성을 파일로 저장하지 않고 Base64 인코딩된 오디오 데이터를 WebSocket으로 전송하고, 클라이언트(모바일 브라우저/앱)에서 <audio> 태그에 직접 로드해서 재생하는 방식이 가장 빠릅니다.
서버 (Node.js)
const express = require("express");
const WebSocket = require("ws");
const speech = require("@google-cloud/speech");
const { Translate } = require("@google-cloud/translate").v2;
const textToSpeech = require("@google-cloud/text-to-speech");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });
const speechClient = new speech.SpeechClient();
const translateClient = new Translate();
const ttsClient = new textToSpeech.TextToSpeechClient();
wss.on("connection", (ws) => {
console.log("Client connected");
let lastText = "";
const request = {
config: {
encoding: "WEBM_OPUS",
sampleRateHertz: 48000,
languageCode: "ko-KR",
},
interimResults: true,
};
const recognizeStream = speechClient
.streamingRecognize(request)
.on("data", async (data) => {
if (data.results[0] && data.results[0].alternatives[0]) {
const text = data.results[0].alternatives[0].transcript;
lastText = text;
// 실시간 번역 (자막용)
const [translation] = await translateClient.translate(text, "en");
ws.send(JSON.stringify({ type: "subtitle", text: translation }));
// 최종 문장일 경우
if (data.results[0].isFinal) {
const [finalTranslation] = await translateClient.translate(text, "en");
ws.send(JSON.stringify({ type: "final", text: finalTranslation }));
// 0.5초 지연 후 TTS 실행
setTimeout(async () => {
const ttsRequest = {
input: { text: finalTranslation },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
const [response] = await ttsClient.synthesizeSpeech(ttsRequest);
ws.send(JSON.stringify({
type: "tts",
audio: response.audioContent.toString("base64"),
}));
}, 500);
}
}
})
.on("error", console.error)
.on("end", () => console.log("STT stream ended"));
ws.on("message", (msg) => {
recognizeStream.write(msg);
});
ws.on("close", () => {
recognizeStream.end();
console.log("Client disconnected");
});
});
server.listen(5000, () => {
console.log("Server running on port 5000");
});
실시간 자막: 발화 중 번역된 텍스트가 화면에 표시됨
최종 번역: 발화 종료 시 문맥 반영된 번역 표시
TTS: Stop 버튼 클릭 후 0.5초 내에 최종 번역 음성이 모바일에서 바로 재생됨
웹 브라우저 기반으로 구현/ngrok http 3000 이용
⚡ 전체 구조
서버 (Node.js, Express, WebSocket)
Google STT → 한국어 음성 → 텍스트 변환
GPT/Google 번역 → 영어 번역
Google TTS → 영어 음성 생성
WebSocket으로 클라이언트에 실시간 전송
클라이언트 (웹 브라우저)
Start 버튼 → 마이크 입력 시작
Stop 버튼 → 발화 종료
실시간 자막 표시 + 최종 번역 표시
최종 번역은 Stop 직후 0.5초 내 TTS로 재생
ngrok
ngrok http 3000실행 → 외부에서 접속 가능한 URL 생성모바일 브라우저에서 ngrok URL 접속 → 바로 통역기 사용 가능
댓글 없음:
댓글 쓰기