음성 입력이 없는 기기에 말로 입력하기

듣고 인식하는 일은 폰이 하고, PC·TV·태블릿은 사용자가 확인한 문장을 치는 Bluetooth 키보드만 봅니다.

소스 기준: 앱 1.0.0 (8) · 2026년 9월 23일

핵심 아이디어. 음성 인식이 꼭 글자를 받는 기기에서 돌아야 할 이유는 없습니다. 도토리 한글 키보드는 이미 폰을 표준 Bluetooth HID 키보드로 만들므로, 음성은 폰에서 인식하고 결과는 사람이 치듯 평범한 키 입력으로 보내면 됩니다. 상대 기기에는 마이크도, 받아쓰기 기능도, 드라이버도, 전용 앱도 필요하지 않습니다.

무엇이 좋아지나

키보드는 받지만 받아쓰기는 못 하는 기기, 혹은 원하는 언어로는 받아쓰기가 안 되는 기기가 많습니다. 반면 손에 든 폰은 대개 할 수 있습니다. 인식을 폰으로 옮기면 키보드를 받는 모든 기기가 음성으로도 입력받는 기기가 됩니다.

사용 가능 환경

폰 OSAndroid 12(API 31) 이상. Android 11 이하에는 온디바이스 인식 API 자체가 없어 마이크 키를 아예 그리지 않습니다.
인식기폰이 온디바이스 음성 인식기를 제공해야 합니다(SpeechRecognizer.isOnDeviceRecognitionAvailable). 없으면 온라인 인식기로 넘어가지 않고 그 사실을 알립니다.
언어 모델선택한 언어의 오프라인 모델이 설치돼 있어야 합니다. 인식 언어는 앱의 현재 한/영 상태를 따라 ko-KR 또는 en-US입니다.
권한마이크 권한. 처음 마이크 키를 누를 때 묻습니다. 거부해도 앱이 기능을 잠그지는 않으며, Android 가 더 묻지 않으면 시스템의 앱 설정에서 마이크 권한을 켭니다.
상대 기기폰을 Bluetooth HID 키보드로 페어링하는 모든 기기. 한글은 선택한 입력 프로필이 다룰 수 있는 IME(예: Windows 한국어 두벌식)가 추가로 필요합니다.
진입점세로 화면의 물음표 앞 마이크 버튼, 가로 커스텀 키보드의 🎤 키, 문장 입력 모드의 마이크 버튼. 셋 모두 같은 음성 창을 열므로 가로·세로 어디서든 흐름이 같습니다.

Android 13 이상에서는 듣기 전에 인식기에게 그 언어 모델이 설치됨·없음·미지원 중 어디에 해당하는지 먼저 묻습니다. 모델이 없거나 아직 내려받는 중이면 시스템에 내려받기를 요청하는 버튼을 함께 보여줍니다. Android 12·12L(API 31–32)에는 이 조회 API 가 없어 첫 실제 시도로 판정하고, 모델이 준비되지 않았으면 폰 설정에서 오프라인 음성 인식 언어를 확인하라고 안내합니다.

기술 관점: 구조적으로 온디바이스 전용

인식기는 API 31 에 추가된 SpeechRecognizer.createOnDeviceSpeechRecognizer로 만듭니다. 먼저 isOnDeviceRecognitionAvailable을 확인하고, false 면 “인식기 없음” 상태로 끝냅니다. 네트워크 서비스를 쓸 수 있는 일반 createSpeechRecognizer로 가는 경로는 일부러 두지 않았습니다. 사용자가 말한 내용이 기기 밖으로 나가는 것은 이 기능의 전제를 바꾸는 일이고, 앱에는 자체 백엔드도 없습니다.

한 바이너리로 세 API 구간(28–30, 31–32, 33+)을 지원하려면 주의가 필요합니다. 앱의 minSdk는 28 이라 API 28–30 폰도 같은 클래스를 로딩합니다. 새 API 의 메서드는 호출 시점에 해석되므로 SDK_INT 분기 뒤에 둘 수 있지만, 새 API 의 타입이 필드나 시그니처에 있으면 분기에 닿기도 전에 클래스 로딩에서 실패합니다. 그래서 컨트롤러는 API 1 부터 있는 RecognitionListener만 구현하고, API 33 타입인 RecognitionSupport·RecognitionSupportCallback은 API 33 분기 안에서만 건드리는 격리된 객체 하나에만 둡니다. 계약 시험이 다른 음성 파일에 그 이름이 새지 않았는지 지킵니다.

언어 태그는 느슨하게 비교합니다. 앱이 ko-KR로 요청해도 인식기는 ko_KR처럼, 대소문자도 섞어 돌려줄 수 있습니다. 문자열을 그대로 비교하면 설치된 언어를 미설치로 오판하므로 설치·내려받는 중·지원 목록과 맞춰보기 전에 표기를 정규화합니다. 지원 여부 조회가 실패하면 미지원으로 단정하지 않고 “알 수 없음”으로 보고 그냥 시도합니다.

자동으로 보내지 않는 이유

중간 인식 결과(partial)는 불안정합니다. 음성이 더 들어오면 인식기가 앞 단어까지 고쳐 씁니다. 일반 폰 키보드라면 글자가 아직 편집 중이라 문제없지만 HID 에서는 다릅니다 — 상대 기기에 도착한 키 입력은 폰이 되돌릴 수 없습니다. 그래서 인식된 글자는 사용자가 확정하기 전까지 보여주기만 하고 치지는 않습니다.

한편 문장마다 확인을 요구하면 말하는 흐름이 끊깁니다. 컨트롤러는 연속 인식으로 이 둘을 함께 풉니다.

확정된 문장이 키 입력이 되기까지

음성은 새 전송 경로를 만들지 않습니다. 확정된 문장은 직접 친 문장 입력과 같은 문장 전송 경로를 탑니다. 글자를 정규화하고 한글·영문 구간으로 나눈 뒤 현대 완성형 한글 음절을 두벌식 자모 키로 분해하고, 호스트 프로필에 필요한 IME 전환까지 계획에 넣습니다. 인코딩할 수 없는 글자가 하나라도 있으면 아무것도 보내지 않습니다 — 상대 기기가 문장의 절반만 받는 일은 없고, 문장은 음성 창에 남아 있어 다시 말할 필요가 없습니다.

HID 입력은 키마다 간격을 두므로 긴 문장은 도착까지 몇 초가 걸립니다. 보내는 동안 버튼은 “전송 중…”으로 바뀌고 다시 누를 수 없어 같은 문장이 두 번 입력되지 않습니다. 문장이 전송에 접수되면 버퍼를 비우고 인식 세션을 새로 열어, 아직 인식기 안에 남아 있던 조각이 다시 실려 오지 않게 합니다. 비우는 시점은 도착이 아니라 접수이므로, HID 전송이 도중에 실패해도 그 문장은 창으로 돌아오지 않습니다.

인식 언어는 앱이 호스트 IME 에 대해 이미 들고 있는 한/영 상태에서 가져옵니다. IME 상태의 주인은 여전히 호스트이고, 화면 표시와 호스트가 어긋나면 한/영 키를 길게 눌러 표시를 맞추는 기존 방법이 음성에도 그대로 적용됩니다. 직접 입력할 때와 같은 이유입니다.

세션 안전성

제약

공식 출처