JunkBox libs/shared-voice 가이드
1. 문서 목적
- 이 문서는 JunkBox 전체에서 재사용 가능한 로컬 음성 런타임 라이브러리인
shared-voice의 현재 구조와 책임 경계를 설명하는 가이드임. - 본 문서는 계획 문서가 아니라 현재 런타임 기준의 구조, 설정 계약, provider 계약, 운영 제약을 이해하기 위한 기준 문서임.
2. 라이브러리 역할
shared-voice는 마이크 입력, STT, 호출어 감지, TTS, 음성 이벤트 모델을 제공하는 공통 음성 라이브러리임.- 앱은 음성 장치 제어, STT/TTS provider 구현, 호출어 매칭 로직을 직접 소유하지 않고
shared-voice를 사용함. - 현재
apps/jeevtrap는interfaces/voice에서shared-voice를 사용해 로컬 음성 호출 PoC 런타임을 실행함. shared-voice는 특정 앱의 실제 호출어 정책 파일을 소유하지 않음.- 실제 호출어, alias, STT/TTS provider, 오디오 장치 설정은 소비 앱의 YAML에서 관리함.
3. 패키지 구조
junkbox_shared_voice/
├── audio/
├── stt/
├── tts/
├── cli.py
├── config.py
├── events.py
├── runtime.py
├── wakeword.py
└── __init__.py
4. 설정 모델
4.1 SharedVoiceConfig
SharedVoiceConfig는 wake, audio, stt, tts 설정을 하나로 묶는 상위 설정 모델임.load_shared_voice_config(path)는 UTF-8 YAML 파일을 읽어SharedVoiceConfig로 변환함.- YAML 구조가 mapping이 아니면 오류로 처리함.
- 앱은
shared-voice안에 설정 파일을 두지 않고, 앱 소유 경로의 YAML을load_shared_voice_config()에 전달함.
4.2 wake 설정
- wake 설정은 호출어 primary, 호출어 alias, 호출 성공 응답 문구, loop 제한 값을 포함함.
wake.primary는 기본 호출어임.wake.aliases는 STT provider가 호출어를 다르게 전사하는 경우를 흡수하기 위한 문자열 목록임.reply.wake_detected는 호출어 감지 후 TTS provider로 출력할 응답 문구임.
4.3 audio 설정
- audio 설정은 sample rate, channel 수, 녹음 chunk 길이, 입력 장치를 포함함.
- 현재 기본 입력 방식은 짧은 chunk 녹음을 반복하고 각 chunk를 STT에 전달하는 구조임.
audio.input_device는sounddevice가 이해할 수 있는 장치명 또는 장치 식별자를 사용함.- PulseAudio/PipeWire 환경에서는
pulse입력 장치를 사용할 수 있음.
4.4 stt 설정
- stt 설정은 STT provider, 모델 크기, device, compute type, language, beam size를 포함함.
- 현재 로컬 STT provider는
faster_whisper를 사용함. - Jeevtrap voice 설정은 CPU
base모델과int8compute type을 기본값으로 사용함. - 호출어 감지를 Whisper STT로 수행하면 상시 CPU 사용량이 발생하므로, 상시 운영 용도에서는
JEEVTRAP_VOICE_ENABLED를 신중하게 제어해야 함.
4.5 tts 설정
- tts 설정은 TTS provider, system command, language, voice name, 합성 step 수, 속도, sample rate, 자동 다운로드 여부를 포함함.
provider=system은spd-say,espeak, macOSsay같은 시스템 TTS 명령을 사용함.provider=supertonic은 Supertonic 로컬 TTS provider를 사용함.- Jeevtrap voice 설정은
supertonic, 한국어ko, voice presetM1을 사용함.
5. STT 구조
5.1 SpeechToTextProvider
- STT provider는
transcribe(audio, sample_rate=...) -> TranscriptResult계약을 구현함. TranscriptResult는 전사 텍스트, 언어, confidence 값을 포함함.
5.2 FasterWhisperSpeechToTextProvider
FasterWhisperSpeechToTextProvider는faster-whisper모델을 lazy load함.- provider 생성 시점에는 모델을 즉시 로드하지 않고, 첫 전사 시 모델을 로드함.
vad_filter=True를 사용해 음성 구간 전사를 보조함.faster-whisper가 설치되지 않은 환경에서 provider를 사용하면 명시적 RuntimeError를 발생시킴.
6. TTS 구조
6.1 TextToSpeechProvider
- TTS provider는
speak(text)계약을 구현함. - 앱은 provider 종류를 직접 분기하지 않고 설정값에 따라 provider를 구성함.
6.2 SystemTextToSpeechProvider
SystemTextToSpeechProvider는 OS 명령 기반 TTS fallback provider임.- Linux에서는
spd-say,espeak순서로 감지함. - macOS에서는
say명령을 감지함. - 사용 가능한 명령이 없으면 표준 출력으로 텍스트를 출력함.
6.3 SupertonicTextToSpeechProvider
SupertonicTextToSpeechProvider는 Supertonic 로컬 TTS를 사용함.- Supertonic TTS 객체와 voice style은 lazy load됨.
- 합성 결과는
sounddevice를 통해 재생함. supertonic,numpy,sounddevice의존성이 없으면 명시적 RuntimeError를 발생시킴.- 첫 합성 시 모델 다운로드 또는 초기화 지연이 발생할 수 있음.
7. Audio 구조
7.1 SoundDeviceAudioRecorder
SoundDeviceAudioRecorder는sounddevice와numpy를 사용해 짧은 mono audio chunk를 녹음함.- 녹음 결과는
AudioChunk(samples, sample_rate, duration_seconds)구조로 반환됨. sounddevice또는numpy가 설치되지 않은 환경에서 사용하면 명시적 RuntimeError를 발생시킴.
7.2 시스템 오디오 전제
- Linux Docker 컨테이너에서 PulseAudio/PipeWire 출력과 입력을 사용하려면 Pulse socket과 cookie 마운트가 필요함.
- Jeevtrap Docker 이미지는 PortAudio/ALSA/PulseAudio 연동 런타임 패키지를 포함함.
- compose 설정은
PULSE_SERVER=unix:/run/user/1000/pulse/native와/run/user/1000/pulse/native, Pulse cookie 마운트를 사용할 수 있음.
8. 호출어 감지
wakeword.py는 한국어/영어 호출어 텍스트를 비교하기 위해 NFKC 정규화, lowercase, 공백/구두점 제거를 수행함.contains_wake_word(transcript, wake_word, aliases)는 primary 호출어와 alias 목록 중 하나가 transcript에 포함되면 true를 반환함.- 호출어 alias는 STT 전사 오인식을 흡수하기 위한 장치이며, 너무 넓은 alias는 오작동 가능성을 높일 수 있음.
- 호출어 변경은 앱 YAML에서 수행하고 라이브러리 기본값을 변경하지 않음.
9. Runtime 구조
WakeWordRuntime은AudioRecorder,SpeechToTextProvider,TextToSpeechProvider,WakeWordRuntimeConfig를 조합함.run_once()는 한 번 녹음하고 STT 결과를 이벤트로 발행한 뒤 호출어가 감지되면 TTS 응답을 출력함.run_forever()는max_loops또는 stop flag가 만족될 때까지run_once()를 반복함.stop()은 내부 stop flag를 설정하며, 현재 실행 중인 녹음 chunk 이후 루프가 종료됨.on_eventhandler가 주어지면VoiceTranscriptReceived,VoiceWakeDetected,VoiceRecognitionFailed이벤트를 앱으로 전달할 수 있음.- 현재 Jeevtrap voice runtime은 blocking
run_forever()를 별도 thread에서 실행함.
10. CLI
shared-voice-wake-demo는 로컬 호출어 PoC CLI임.--config로 앱 소유 voice YAML을 전달할 수 있음.- CLI 옵션은 YAML 값을 override할 수 있음.
예시:
uv run --package junkbox-shared-voice --extra poc shared-voice-wake-demo \
--config apps/jeevtrap/var/voice.yml
11. 의존성 extra
audioextra는numpy,sounddevice를 포함함.local-sttextra는faster-whisper를 포함함.supertonicextra는 Supertonic TTS를 포함함.pocextra는audio,local-stt,supertonic실행에 필요한 의존성을 함께 포함함.
12. Jeevtrap 사용 계약
apps/jeevtrap/var/voice.yml은 Jeevtrap voice runtime의 기본 YAML임.JEEVTRAP_VOICE_ENABLED가 true이면interfaces/voice.JeevtrapVoiceRuntime이shared-voice런타임을 시작함.JEEVTRAP_VOICE_CONFIG_YAML_PATH가 설정되면 기본 YAML 경로를 override함.- 운영 기본값은 voice disabled임.
- voice runtime은 현재 호출어 감지와 TTS 응답까지만 담당함.
- 음성 명령 의도 판별, 실행 승인, 스크립트 실행 연결은 별도 상태 머신과 승인 흐름을 필요로 함.