본문으로 건너뛰기

API 참조

핵심 컴포넌트

KlleonOndeviceSdk

모든 기능을 제공하는 메인 SDK 클래스입니다.

// 싱글톤으로 관리 (권장)
object SdkConfig {
val sdk = KlleonOndeviceSdk()
}
val klleonOndeviceSdk = SdkConfig.sdk

KlleonSdkView

SDK 콘텐츠를 렌더링하기 위한 커스텀 Android View입니다.

@Composable
fun SdkViewComponent() {
var workerView by remember { mutableStateOf<KlleonSdkView?>(null) }

AndroidView(
factory = { context ->
KlleonSdkView(context).also { view ->
workerView = view
}
},
modifier = Modifier.fillMaxSize()
)
}

setRenderRotation()

TV 등에서 화면 회전이 필요할 때 사용합니다.

klleonSdkView.setRenderRotation(270) // 0, 90, 270도 지원

ResourceManager

리소스 초기화 및 해제를 담당하는 클래스입니다. play() 전에 반드시 init()으로 리소스를 준비해야 합니다.

val resourceManager = ResourceManager()

init()

아바타 리소스를 캐시 디렉토리로 복사하고, YUV 변환 및 네이티브 메모리 적재를 수행합니다.

resourceManager.init(
context: Context,
mp4FileName: String,
npzFileName: String,
styleFileName: String = "",
faceCropYuvFileName: String = "",
faceCropYuvMetaFileName: String = "",
avatarId: String = "",
onSuccess: () -> Unit,
onError: (Throwable) -> Unit
)

매개변수:

매개변수타입필수설명
contextContextO현재 컨텍스트
mp4FileNameStringO아바타 MP4 파일명 (assets 내)
npzFileNameStringO아바타 NPZ 파일명 (assets 내)
styleFileNameStringX스타일 파일명 (기본값: "")
faceCropYuvFileNameStringX사전 추출된 face crop YUV 파일명 (기본값: ""). 제공 시 warpAffine 단계 스킵하여 추론 경로 단축 (LIPSYNC-007, v0.6.0)
faceCropYuvMetaFileNameStringXface crop YUV 메타 파일명 (기본값: ""). faceCropYuvFileName과 함께 사용
avatarIdStringX아바타 ID. 동일 아바타 재진입 시 버퍼를 재사용하여 즉시 onSuccess 반환
onSuccess() -> UnitO리소스 준비 완료 콜백
onError(Throwable) -> UnitO에러 콜백

예제:

resourceManager.init(
context = this,
mp4FileName = "loop_sample.mp4",
npzFileName = "loop_sample.npz",
styleFileName = "loop_sample.style",
avatarId = "character_001",
onSuccess = {
// 준비 완료 — play()는 suspend이므로 코루틴에서 호출
CoroutineScope(Dispatchers.IO).launch {
klleonOndeviceSdk.play(context, sdkView, sdkKey, avatarId, language)
}
},
onError = { error ->
Log.e("SDK", "리소스 준비 실패", error)
}
)
버퍼 재사용

동일한 아바타(avatarId)로 init()을 재호출하면 이미 준비된 버퍼를 재사용하여 즉시 onSuccess를 반환합니다. 다른 아바타로 전환 시에는 이전 버퍼를 해제하고 새로 할당합니다.

deinit()

네이티브 YUV 버퍼 메모리를 해제하고 리소스 상태를 초기화합니다.

resourceManager.deinit()
멱등성 보장

deinit()은 여러 번 호출해도 안전합니다. 이미 해제된 상태에서 호출하면 no-op입니다.

호출 규칙

  1. init() — 아바타 선택 완료 시 호출. 아바타 변경 시 이전 버퍼를 자동 해제합니다.
  2. play()init()onSuccess 콜백이 왔을 때만 호출 가능
  3. deinit() — 앱 완전 종료 시 호출. finish()는 버퍼를 유지하여 동일 아바타 재진입을 최적화합니다.

SDK 메서드

play()

AI 아바타 비디오 재생을 시작합니다. suspend 함수이므로 코루틴 내에서 호출해야 합니다.

suspend fun play(
context: Context,
klleonSdkViewInstance: KlleonSdkView?,
sdkKey: String,
avatarId: String,
language: String
)

매개변수:

매개변수타입필수설명
contextContextO현재 컨텍스트
klleonSdkViewInstanceKlleonSdkView?O렌더링 뷰
sdkKeyStringOSDK 인증 키
avatarIdStringOAI 아바타 ID
languageStringO언어 코드 (ko_kr, en_us, ja_jp)

예제:

CoroutineScope(Dispatchers.IO).launch {
workerView?.let { view ->
klleonOndeviceSdk.play(
context = this@Activity,
klleonSdkViewInstance = view,
sdkKey = "YOUR_SDK_KEY",
avatarId = "character_001",
language = "ko_kr"
)
}
}

connect()

WebSocket 재연결을 수행합니다.

klleonOndeviceSdk.connect()

finish()

SDK를 중지하고 모든 리소스를 정리합니다. suspend 함수이므로 코루틴 내에서 호출해야 합니다. 내부 버퍼는 동일 아바타 재진입 시 재사용을 위해 유지됩니다 (soft reset).

// Activity 종료 시
CoroutineScope(Dispatchers.Default).launch {
klleonOndeviceSdk.finish()
}

awaitFinish()

진행 중인 finish()가 완료될 때까지 대기합니다. 종료 중이 아니면 즉시 반환됩니다. finish() 후 빠르게 play()를 재호출하는 경우, ResourceManager.init() 전에 호출하여 리소스 경합을 방지합니다.

// 빠른 재진입 시
CoroutineScope(Dispatchers.Default).launch {
klleonOndeviceSdk.awaitFinish() // 이전 종료 완료 대기
ResourceManager.init(context, ...)
}

메시지 API

sendMessage()

SDK의 메시징 시스템을 통해 메시지를 전송합니다.

klleonOndeviceSdk.sendMessage(text: String)

예제:

klleonOndeviceSdk.sendMessage("안녕하세요")

sendMessageEcho()

입력 텍스트를 그대로 TTS + 립싱크로 재생합니다 (LLM 바이패스). 고정 안내 멘트 등에 사용합니다.

klleonOndeviceSdk.sendMessageEcho(text: String)

sendChangeLanguage()

대화 중 언어를 동적으로 변경합니다.

klleonOndeviceSdk.sendChangeLanguage(language: String)

예제:

// 영어로 변경
klleonOndeviceSdk.sendChangeLanguage("en")

sendStopSpeech()

아바타의 현재 발화를 즉시 중단합니다.

klleonOndeviceSdk.sendStopSpeech()

Wakeword API

STT 인식 결과에서 등록된 wakeword를 감지하여 콜백을 전달합니다. Wakeword가 매칭되면 해당 STT 결과는 일반 대화 입력(STT_RESULT)에서 제외됩니다.

Standby 연동 정책

Wakeword 매칭은 기본적으로 비활성화 상태입니다. enterStandby() 호출 시 자동으로 활성화되고, exitStandby() 호출 시 자동으로 비활성화됩니다. registerWakewords()로 wakeword를 등록하더라도 standby 모드에 진입해야 매칭이 동작합니다.

STT 언어와 wakeword

play(language) 또는 sendChangeLanguage(language)로 설정한 언어가 STT 인식 언어로도 적용됩니다. Wakeword 매칭은 해당 언어의 STT 인식 결과에서 동작하므로, 각 언어의 STT가 인식할 수 있는 wakeword를 등록해야 합니다.

예시:

  • 한국어 STT → "영어", "일본어", "니혼고" 등 한국어 발음 wakeword
  • 일본어 STT → "韓国語", "英語", "コリアン" 등 일본어 인식 wakeword
  • 영어 STT → "Korean", "Japanese" 등 영어 wakeword

registerWakewords()

Wakeword를 등록합니다. 기존 wakeword를 전체 교체합니다. 등록만으로는 매칭이 동작하지 않으며, enterStandby() 진입 시 활성화됩니다.

fun registerWakewords(wakewords: Map<String, String>)

매개변수:

매개변수타입설명
wakewordsMap<String, String>wakeword → 태그 맵. Wakeword가 감지되면 태그 값이 콜백으로 전달됨

예제:

// 언어 전환용 wakeword 등록
klleonOndeviceSdk.registerWakewords(mapOf(
"니혼고" to "ja_jp", "일본어" to "ja_jp",
"잉글리쉬" to "en_us", "영어" to "en_us",
"한국어" to "ko_kr", "코리안" to "ko_kr",
))

clearWakewords()

등록된 wakeword를 전체 초기화합니다. 초기화 후에는 wakeword 매칭이 동작하지 않으며, 모든 STT 결과가 STT_RESULT로 전달됩니다.

klleonOndeviceSdk.clearWakewords()

wakewordDetected

Wakeword 매칭 결과를 방출하는 SharedFlow입니다.

val wakewordDetected: SharedFlow<WakewordResult>

WakewordResult:

필드타입설명
wakewordString매칭된 wakeword 원문 (예: "니혼고")
tagString등록 시 지정한 태그 (예: "ja_jp")
fullTextStringSTT가 인식한 전체 텍스트

예제:

// Wakeword 감지 시 언어 전환
LaunchedEffect(Unit) {
klleonOndeviceSdk.wakewordDetected.collect { result ->
// result.wakeword = "니혼고", result.tag = "ja_jp"
klleonOndeviceSdk.sendChangeLanguage(result.tag)
}
}

SDK는 wakeword가 무엇을 의미하는지 관여하지 않습니다. 태그 값은 앱이 자유롭게 정의할 수 있으며, 언어 코드 외에도 "mode_quiet", "stop" 등 어떤 문자열이든 사용 가능합니다.


대기화면(Standby) API

enterStandby()

대기화면 모드로 진입합니다. 파이프라인 전체를 정리(큐 비우기, 오디오 버퍼 폐기, 타이밍 초기화)한 후 비디오 디코딩과 오디오 재생을 일시정지합니다. 리소스는 유지됩니다.

klleonOndeviceSdk.enterStandby()

exitStandby()

대기화면 모드를 해제합니다. 비디오 디코딩과 오디오 재생을 재개합니다.

klleonOndeviceSdk.exitStandby()

isStandby()

대기화면 모드 상태인지 확인합니다.

val isStandby: Boolean = klleonOndeviceSdk.isStandby()

setIdleTimeout(timeoutMs)

idle timeout을 활성화합니다. 아바타 발화 종료(TTS_END) 후 지정 시간 동안 무활동 시 자동으로 enterStandby()를 호출합니다. STANDBY_ENTER 이벤트가 sdkState로 발행된 후 대기화면에 진입합니다.

// 기본값 60초
klleonOndeviceSdk.setIdleTimeout()

// 커스텀 timeout (30초)
klleonOndeviceSdk.setIdleTimeout(30_000L)
정보

play() 호출 전후 어느 시점에든 호출 가능합니다. 호출하지 않으면 자동 대기화면 기능은 비활성화 상태입니다.

disableIdleTimeout()

idle timeout을 비활성화합니다. 활성화된 타이머가 있으면 취소됩니다.

klleonOndeviceSdk.disableIdleTimeout()

isResponding: StateFlow<Boolean>

아바타가 응답 준비 중이거나 발화 중이면 true. PREPARING_RESPONSE 수신 시 true, TTS_END 또는 ERROR 수신 시 false로 전환됩니다.

// 현재 값 확인
if (klleonOndeviceSdk.isResponding.value) {
// 아바타 응답 중
}

// Flow로 구독
klleonOndeviceSdk.isResponding.collect { responding ->
// UI 업데이트
}

음성 인식 API

startVoiceRecognition()

음성 인식을 시작합니다. 마이크 녹음을 시작하고 WebSocket으로 START_VOICE 신호를 전송합니다. 무음이 감지되면 자동으로 녹음을 종료하고 END_VOICE 신호를 전송합니다.

fun startVoiceRecognition(
config: SilenceDetectionConfig = SilenceDetectionConfig.default()
): Boolean

매개변수:

  • config: 무음 감지 설정 (선택, 기본값: SilenceDetectionConfig.default())

반환값: 성공 여부 (Boolean)

예제:

// 기본 설정으로 음성 인식 시작
val success = klleonOndeviceSdk.startVoiceRecognition()

// 커스텀 설정으로 시작
val customConfig = SilenceDetectionConfig.default().copy(
silenceTimeoutMs = 2000L // 무음 감지 시간 2초
)
val success = klleonOndeviceSdk.startVoiceRecognition(customConfig)

stopVoiceRecognition()

음성 인식을 정지합니다.

klleonOndeviceSdk.stopVoiceRecognition()

isVoiceRecognizing()

음성 인식 중인지 확인합니다.

val isRecognizing: Boolean = klleonOndeviceSdk.isVoiceRecognizing()

voiceRecognitionMode

음성 인식 모드를 설정합니다. play() 호출 전에 설정해야 합니다.

klleonOndeviceSdk.voiceRecognitionMode = VoiceRecognitionMode.ALWAYS_ON

모드:

모드설명
DISCRETE단발 모드 (기본값). 수동으로 시작/종료
ALWAYS_ON상시 리스닝. play() 후 자동 시작, 세션 유지

startAlwaysOnListening()

ALWAYS_ON 리스닝을 수동으로 시작합니다.

val success: Boolean = klleonOndeviceSdk.startAlwaysOnListening()

isAutoListening()

ALWAYS_ON 자동 리스닝 중인지 확인합니다.

val isListening: Boolean = klleonOndeviceSdk.isAutoListening()

오디오 입력 유틸리티

hasRecordAudioPermission()

RECORD_AUDIO 권한이 있는지 확인합니다.

val hasPermission: Boolean = klleonOndeviceSdk.hasRecordAudioPermission()

hasUsbMicrophone()

USB 마이크가 연결되어 있는지 확인합니다.

val hasUsb: Boolean = klleonOndeviceSdk.hasUsbMicrophone()

상태 관리

sdkState

SDK 상태 업데이트를 JSON 문자열로 방출하는 SharedFlow입니다.

klleonOndeviceSdk.sdkState: SharedFlow<String>

사용법:

klleonOndeviceSdk.sdkState.collect { state ->
val messageToAdd = try {
val json = JSONObject(state)
json.getString("message")
} catch (t: Throwable) {
state.toString()
}

// 상태 업데이트 처리
updateUI(messageToAdd)
}

상태 형식 (JSON):

{
"message": "",
"chat_type": "ACTIVATE_VOICE",
"time": "2025-09-03T05:42:08.394975295",
"id": "99dd3ab3-53c4-4418-bee4-b1fbaea272aa"
}

주요 chat_type 값:

chat_type설명
WSS_OPENWebSocket 연결 성공
WSS_FAILUREWebSocket 연결 실패
WSS_CLOSINGWebSocket 연결 종료 중
WSS_CLOSEDWebSocket 연결 종료됨
WSS_RETRY_OVER최대 재시도 초과
WSS_NOT_CONNECTED미연결 상태
ACTIVATE_VOICE음성 입력 수신 준비 완료
TEXT서버로부터 텍스트 응답 수신
RESPONSE_IS_ENDED서버 응답 스트림 종료
TTS_STARTTTS 음성 재생 시작
TTS_ENDTTS 음성 재생 완료 (발화 종료 감지에 사용)
SHOW_AVATAR아바타 표시 준비 완료
USER_SPEECH_STARTED사용자 발화 시작 감지
USER_SPEECH_STOPPED사용자 발화 종료 감지
PREPARING_RESPONSE서버 응답 준비 중
STT_RESULT음성 인식 최종 결과
STT_PARTIAL음성 인식 중간 결과
STT_ERROR음성 인식 에러
STT_STATUS음성 인식 상태 변화 알림 (준비 중/재연결 중/준비 완료)
WORKER_DISCONNECTED소켓 연결 종료
SPEECH_START아바타 발화 시작
SPEECH_END아바타 발화 종료
STOP_RESPONSE_MESSAGE현재 아바타 발화 중단

STT_STATUS 상세

음성 인식 세션의 일시적 상태 변화를 알려줍니다. 토큰 기반 인증을 사용하는 STT 제공자(Azure 등)에서 발생합니다.

상태message 값의미발생 시점
Preparing음성 인식 준비 중토큰 대기 중으로 인식 시작이 지연됨토큰 미수신 상태에서 음성 인식 시작 시
Recovering음성 인식 재연결 중인증 실패로 토큰 재발급 대기 중인식 중 인증 토큰 만료 시
Ready음성 인식 준비 완료복구 완료, 음성 인식 재개토큰 재발급 후 인식 재시작 시

STT_STATUS는 일시적 상태 알림입니다. 음성 인식 준비 중 또는 음성 인식 재연결 중 수신 시 사용자에게 토스트 등으로 안내하고, 음성 인식 준비 완료 수신 시 자연스럽게 해제하면 됩니다.

recorderState

녹음기 상태를 실시간으로 구독할 수 있는 StateFlow입니다.

klleonOndeviceSdk.recorderState: StateFlow<OboeRecorderState>

상태 값:

상태설명
Idle초기 상태
Starting시작 중
Recording(deviceId, deviceName, deviceType)녹음 중
Stopping정지 중
Error(message)에러 상태

SilenceDetectionConfig

음성 인식 시 무음 감지 동작을 설정합니다.

import io.klleon.ondevice.audio.oboe.SilenceDetectionConfig

필드:

필드타입기본값설명
silenceThresholdFloat0.02무음 판단 레벨 임계값
speechThresholdFloat0.05음성 판단 레벨 임계값
speechTimeoutMsLong5000음성 입력 대기 시간 (ms)
silenceTimeoutMsLong2000음성 후 무음 감지 시간 (ms)
maxRecordingTimeMsLong60000최대 녹음 시간 (ms, 0=무제한)
preBufferMsInt300음성 시작 전 오디오 보존 (ms)
preBufferDelayMsLong300프리버퍼 플러시 지연 (ms)

프리셋:

프리셋특징
SilenceDetectionConfig.default()기본 설정
SilenceDetectionConfig.fast()빠른 응답 (speechTimeout 3초, silenceTimeout 1.5초)
SilenceDetectionConfig.longSpeech()긴 발화 (speechTimeout 7초, silenceTimeout 3초, maxRecording 120초)
SilenceDetectionConfig.noisyEnvironment()소음 환경 (임계값 상향: silence 0.05, speech 0.10)

예제:

// 프리셋 사용
klleonOndeviceSdk.startVoiceRecognition(SilenceDetectionConfig.fast())

// 커스텀 설정
val config = SilenceDetectionConfig.default().copy(
speechTimeoutMs = 3000L,
maxRecordingTimeMs = 120_000L
)
klleonOndeviceSdk.startVoiceRecognition(config)

프로퍼티

klleonSdkView

현재 렌더링 뷰 참조입니다.

var klleonSdkView: KlleonSdkView?

Companion Object

프로퍼티타입설명
VERSIONStringSDK 버전
playingBooleanSDK 재생 상태
sendVoiceBoolean음성 전송 상태

인스턴스 프로퍼티

프로퍼티타입설명
isStoppingBooleanSDK가 현재 종료 중인지 여부 (finish() 진행 중이면 true)

오류 처리

모든 SDK 메서드는 try-catch 블록으로 감싸야 합니다:

try {
klleonOndeviceSdk.sendMessage(message)
} catch (e: Exception) {
Log.e("KlleonSDK", "작업 실패", e)
// 적절하게 오류 처리
}

라이프사이클 관리

play()finish()suspend 함수입니다. Activity 라이프사이클에서는 코루틴으로 래핑하여 호출합니다.

class ChatActivity : ComponentActivity() {
private val sdk = SdkConfig.sdk

override fun onDestroy() {
super.onDestroy()
// finish()는 suspend이므로 코루틴에서 실행
CoroutineScope(Dispatchers.Default).launch {
sdk.finish() // SDK 정지 (버퍼는 재사용을 위해 유지)
}
ResourceManager.deinit() // 앱 완전 종료 시에만 호출
}
}

빠른 재진입 (finish → play)

finish() 후 곧바로 play()를 호출하면 리소스 경합이 발생할 수 있습니다. awaitFinish()를 사용하여 이전 종료가 완료된 후 진행하세요:

// 캐릭터 선택 화면에서 새 아바타 시작 시
CoroutineScope(Dispatchers.Default).launch {
sdk.awaitFinish() // 이전 finish()가 진행 중이면 완료 대기
ResourceManager.init(context, mp4, npz, style, avatarId,
onSuccess = { /* ChatActivity 시작 → play() */ },
onError = { /* 에러 처리 */ }
)
}