iOS · Real-Time Translation

귀로 듣는 실시간 동시통역,
무선 이어폰 하나면 충분합니다

실시간 영어·일본어·중국어를 한국어로 통역합니다.
소리는 에어팟·갤럭시버즈·MFi 보청기로 출력하고
화면에는 입력 언어를 자동 감지한 자막이 송출됩니다.
말하는 도중에도 실시간으로 통역을 제공해 발화가 끝날 때까지 기다릴 필요가 없습니다.

이주형 · Lee Ju Hyeong기획부터 iOS·백엔드·인프라까지 1인 개발
iOS 17+ · SwiftUI LiveKit WebRTC Realtime Voice AI Python Agent
StarLink
통역 중
탭하여 통역 시작 / 정지
AirPods 출력
연속
스트리밍 통역
<1s
엔드-투-엔드 지연 · 개발 환경 체감
3→1
영·일·중 → 한국어 통역
LOCAL
자체 파서 · 서버 왕복 없이 로컬에서 처리
3,340
코드 라인 · Swift 2,483 / Python 857
Why I built it

발화가 끝나야 번역이 시작되는 방식으로는 대화의 흐름을 따라가기 어려웠습니다.

청각장애 당사자로서 직접 겪은 문제를 해결하기 위해, 말하는 동안 번역도 함께 이어지는 StarLink를 만들었습니다. 번역 음성은 무선 이어폰과 보청기로 바로 전달되며, iOS 앱부터 Python 백엔드와 인프라까지 혼자 구현했습니다.

진행 형태1인 개발기획 · iOS · 백엔드 · 인프라 전담
개발 기간2026.06약 3일 집중 개발
구현 방식AI 초안 + 직접 디버깅오류 로그·프로토콜 동작을 대조해 수정
현재 상태개인 빌드 (데모용)무료 인증서 · 7일 재서명 · 앱스토어 미심사
라이선스MIT · Open Source
What it does

핵심 기능

핸즈프리로 외국어 대화에 자연스럽게 참여하도록, 통역·자막·기록을 하나의 흐름으로 묶었습니다.

모든 무선 기기 지원

에어팟, 갤럭시버즈, 일반 블루투스 이어버드, MFi 보청기를 동일하게 처리합니다. 기기별 설정 없이 연결된 무선 기기로 통역 음성이 자동 라우팅됩니다.

실시간 동기 자막

음성 출력과 동시에 번역 자막이 화면에 표시됩니다. 입력 언어를 자동 감지해 [영어]·[일본어] 글래스모피즘 배지로 함께 보여줍니다.

회의록 기록 모드

통역 모드와 별개로, 한국어 포함 모든 언어를 0ms 지연으로 받아쓰는 STT 전용 모드. 대화 히스토리를 .txt로 내보내 공유할 수 있습니다.

BYOK 설정 UI

톱니바퀴 버튼으로 LiveKit URL·토큰을 직접 입력해 UserDefaults에 저장. 서버 비용 없이 개인 키로 동작하는 앱스토어 배포 시나리오를 지원합니다.

토큰 서버 보안

aiohttp 기반 토큰 서버가 1시간 TTL 단기 JWT를 발급. iOS는 실행 시 동적 토큰을 받아오고, 없으면 안전하게 폴백합니다.

연속 스트리밍 WebRTC

LiveKit Cloud의 미디어 라우팅 엔진으로 양방향 오디오·데이터를 스트리밍. 마이크 입력부터 번역 음성 출력까지 하나의 연속 스트림으로 연결합니다.

Engineering Deep-Dive

해결한 4대 기술 난제

실시간 오디오 + WebRTC + 음성 AI가 충돌하는 지점들을 하나씩 깨면서 만든 프로젝트입니다.

01

블루투스 & MFi 오디오 라우팅 제어 AVAudioSession · WebRTC

문제

LiveKit의 자동 오디오 구성이 AVAudioSession 카테고리를 재설정해, 연결된 이어폰 대신 아이폰 내장 리시버로 오디오가 라우팅. 직접 세션을 조작하면 WebRTC 세션 관리와 충돌해 비동기 오디오 스레드 데드락 발생.

해결

출력 경로 제어를 raw 오버라이드 대신 LiveKit 공식 API(isSpeakerOutputPreferred)로 통제. 보청기 연결 시 스피커 우선을 끄고 짧은 블루투스 정착 지연을 넣어 AUIOClient_StartIO (-3001) 데드락을 회피.

02

음향 피드백 루프 방지 Echo Cancellation

문제

이어폰으로 재생되는 통역 음성이 마이크로 다시 새어 들어가, AI가 자기 통역을 듣고 재통역하는 무한 에코 루프(then, and then… 도배) 발생. 마이크 트랙을 물리적으로 뮤트하면 WebRTC 오디오 디바이스 락 위험.

해결

마이크 트랙을 물리적으로 건드리지 않고, Silero·VAD·turn-taking을 전부 제거. Gemini의 자동 발화 감지를 끄고(AutomaticActivityDetection(disabled=True)) 오디오를 연속 스트림으로 처리하며, generate_reply를 가로채 ActivityEnd 전송을 막아 모델이 자기 출력을 재통역하지 않게 함.

03

음성-자막 모달리티 동기화 Gemini Live

문제

음성-대-음성 모델은 기본값에서 전사(transcription) 스트림을 보내지 않아 자막이 번역 생성 중… 상태에 멈춤. SDK가 같은 비동기 제네레이터를 이중 소비하며 스트림이 프리징되는 문제도 동반.

해결

세션 초기화 시 입·출력에 AudioTranscriptionConfig()를 주입해 전사 토큰을 강제 방출. emit 몽키패치와 스트림 래퍼로 부작용 없이 자막 텍스트만 복제 전송해 오디오·자막을 동시 복구.

04

무지연 입력 언어 감지 0ms Unicode Parser

문제

다국어 대화의 원문 언어를 표시하려면 언어 감지가 필요한데, 외부 번역 API를 쓰면 허용 불가한 네트워크 지연이 추가됨.

해결

유니코드 블록(한글·가나·CJK 한자·라틴)을 분석하는 0ms 경량 파서 detect_language를 백엔드에 구현. 언어 코드를 LiveKit 데이터 채널로 전송해 실시간 언어 배지를 렌더링.

backend/agent.py — 0ms 유니코드 언어 감지
def detect_language(text: str) -> str:
    if not text:
        return "unknown"
    text = text.strip()

    # Hangul (Korean)
    if any('가' <= c <= '힣' for c in text):
        return "ko"
    # Hiragana / Katakana (Japanese)
    if any('぀' <= c <= 'ヿ' for c in text):
        return "ja"
    # Chinese Hanzi
    if any('一' <= c <= '鿿' for c in text):
        return "zh"
    # English / Latin
    latin = sum(1 for c in text if 'a' <= c.lower() <= 'z')
    return "en" if latin > len(text) * 0.3 else "unknown"
System Design

아키텍처 & 데이터 흐름

아이폰 마이크에서 시작해 LiveKit Cloud를 거쳐 Python 에이전트의 Gemini 통역까지, 양방향 WebRTC 스트림으로 연결됩니다.

통역 음성(KO) + 자막 데이터 · downlink 외국어 음성 EN · JA · ZH iPhone App SwiftUI · LiveKit SDK LiveKit Cloud WebRTC · Data Ch. Python Agent Gemini 3.5 Live AirPods · 보청기 한국어 음성 + 자막 audio uplink →
① iPhone — StarLink App

오디오 캡처 & 라우팅

마이크가 주변 외국어 음성을 포착하고, LiveKit SDK가 로컬 오디오 트랙을 publish. 오디오 세션 관리자가 출력을 무선 기기로 라우팅합니다.

SwiftUILiveKit SDKMFiAudioManagerAVAudioSession
WebRTC · Audio Track ↑
② LiveKit Cloud

저지연 미디어 라우팅 엔진

양방향 오디오·데이터 스트림을 룸 단위로 중계. 자막은 caption 토픽의 데이터 채널로 전달됩니다.

WebRTCData ChannelJWT Token
WebRTC · Audio + Data ↕
③ Backend Agent — Python

Gemini 3.5 Live 통역

livekit-agents가 오디오를 구독해 Gemini Live로 EN·JA·ZH → KO 통역. 언어 감지·전사 스트림을 파싱해 다시 publish합니다.

livekit-agentsGemini LiveNo-VAD Streamingaiohttp
Debugging Depth

통역을 멈추게 한 오류를 추적했습니다

WebSocket 세션 충돌과 CoreAudio 데드락을 로그·프로토콜 동작과 대조해 원인을 찾고 수정했습니다.

1007WebSocket
backend/agent.py

음성 모델이 세션을 끊어버림

음성-대-음성 Gemini Live에 SDK의 generate_reply 호출, 그리고 모드 전환 시 update_instructions를 모델이 거부해 1007 (invalid argument)로 WebSocket이 종료.

generate_reply 몽키패치 + 세션 중 update_instructions 미호출(모드는 상태만 추적)
-3001CoreAudio
Managers/LiveKitStreamManager.swift

블루투스 연결 직후 오디오 데드락

BT 기기 연결 직후 AUIOClient_StartIO를 너무 빨리 호출하면 오디오 엔진이 -3001 (kAudioEngineErr)로 멈춤.

짧은 블루투스 정착(settle) 지연을 삽입해 회피
-50OSStatus
Managers · 오디오 라우팅

스피커 강제 전환 실패

표준 overrideOutputAudioPort(.speaker)를 직접 호출하면 LiveKit 자동 세션 관리에 덮어써지며 OSStatus -50 (invalid param) 발생.

raw 오버라이드를 버리고 자동 구성에 위임 + isSpeakerOutputPreferred로 제어
See it in action

데모

실기기에서 에어팟으로 통역을 들으며 자막이 실시간으로 쌓이는 흐름입니다.

이어폰 연결 → 통역 시작

무선 기기를 페어링하고 토글 버튼 한 번이면 주변 외국어가 한국어로 귀에 들어옵니다.

언어 배지 + 자막 동기화

입력 언어가 감지돼 배지로 표시되고, 통역 음성과 자막이 동시에 쌓입니다.

기록 모드 전환 → 내보내기

회의록 모드로 받아쓴 대화를 .txt로 공유 시트에 바로 전달합니다.

무료 계정 사이드로딩

유료 멤버십 없이 개인 Apple ID로 실기기에 7일 서명 빌드를 올려 데모를 녹화할 수 있습니다.

Build Journey

시행착오 포스트모템

실제 개발 로그(progress.md)에 남긴, 막힌 지점과 그걸 뚫은 방법의 기록입니다. 결과보다 과정이 더 많은 걸 말해줍니다.

마이크 에코 루프 — then, and then… 무한 도배

초기엔 VAD로 마이크를 즉시 여닫자 통역 잔향이 재유입돼 번역이 정지. 백엔드 상태 메시지 기준으로 바꿨더니 조기 언뮤트로 또 막힘.

→ 물리적 mute·VAD를 전부 버리고, 자동 발화 감지 비활성화 + generate_reply 차단으로 스트리밍 전용 전환

오디오가 보청기 대신 폰 리시버로 새는 문제

자동 구성·커스텀 콜백 모두 WebRTC가 세션을 수화기 모드로 덮어써 무음 발생. overrideOutputAudioPort 직접 호출은 OSStatus -50 에러.

→ 자동 구성(isAutomaticConfigurationEnabled=true)에 위임하고 isSpeakerOutputPreferred로 출력 경로 제어

비동기 스트림 이중 소비로 자막·오디오 동반 정지

백엔드와 SDK가 같은 message_stream 제네레이터를 동시에 소비하며 태스크 프리징.

→ emit 몽키패치 + intercept 래퍼로 부작용 없이 자막만 복제, 양쪽 완전 복구

인터럽션 차단 시 세션 시작 즉시 크래시

초기엔 서버 사이드 발화 감지 + allow_interruptions=False로 가로채기를 막으려다 SDK 내부 ValueError·오디오 폐기 크래시.

→ 모든 speech handle에 allow_interruptions=True 부여 + Gemini 자동 발화 감지 비활성화(로컬 VAD 없이)로 안정화
Tech Stack

기술 스택

iOS Client

  • Swift 5.9
  • SwiftUI
  • AVFoundation
  • XcodeGen

Realtime / Media

  • LiveKit WebRTC
  • livekit-agents
  • Data Channel
  • 연속 스트리밍 (No VAD)

AI / Backend

  • Gemini 3.5 Live
  • Python 3.11+
  • aiohttp
  • asyncio

Security / Ops

  • JWT (1h TTL)
  • Secrets.plist
  • BYOK / UserDefaults
  • MIT License

핸즈프리로 언어 장벽을 넘다

StarLink는 실시간 오디오·WebRTC·음성 AI의 까다로운 충돌 지점을 직접 부딪혀 해결한 iOS 프로젝트입니다.