Реализация мультимодального AI-ввода (текст + аудио) в мобильном приложении
Голосовые сообщения в мессенджерах — привычная вещь. Но когда пользователь хочет не просто расшифровку, а смысловой ответ на сказанное — нужна связка: захват аудио → транскрипция (или нативный аудио-ввод в модель) → LLM с контекстом. Это три разных технических слоя, и у каждого свои подводные камни.
Два архитектурных пути
Путь 1: STT → LLM. Whisper API или аналоги преобразуют аудио в текст, текст уходит в messages[]. Работает с любой LLM, дёшево, предсказуемо. Проблема — двойная задержка: сначала ждём транскрипцию (1–3 с для 30-секундного фрагмента), потом ответ модели. Пользователь смотрит в экран 5–10 секунд.
Путь 2: нативный аудио-ввод. GPT-4o Audio Preview, Gemini 1.5 Pro принимают input_audio прямо в content[]. Задержка меньше, модель «слышит» интонацию, паузы, акцент. Ограничение — формат: OpenAI требует PCM16 или MP3, Gemini — FLAC, MP3, WAV, OGG. На устройстве нужна конвертация.
Выбор зависит от задачи. Голосовой ассистент для общения — путь 2. Транскрипция совещаний с последующим анализом — путь 1 с батч-обработкой.
Запись аудио: откуда приходят баги
На Android захват через MediaRecorder прост, но AudioRecord нужен, когда требуется PCM в реальном времени (стриминг к Whisper через WebSocket). MediaRecorder сохраняет в файл — удобно для коротких голосовых, неудобно для живого потока. Типичный краш: IllegalStateException: start called in invalid state — это вызов start() до prepare() или повторный start() без reset(). Не забывайте освобождать в onPause(), иначе другие приложения потеряют микрофон.
На iOS: AVAudioEngine для PCM-стриминга, AVAudioRecorder для файлов. Проблема, которую все встречают — AVAudioSession конфигурация. Если не выставить .record категорию до старта, запись либо тихая, либо идёт через динамик вместо микрофона. А с iOS 17 нужен NSMicrophoneUsageDescription даже для симулятора.
Формат по умолчанию у AVAudioRecorder — CAF. Whisper его не принимает. Нужно либо конвертировать через AVAssetExportSession (асинхронно, добавляет задержку), либо сразу настроить AVAudioRecorder на M4A/FLAC.
Реализация стримингового STT
Для живой расшифровки (пользователь говорит — текст появляется на экране) используем WebSocket к Whisper Streaming или Deepgram. На Android:
val audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
16000, // 16kHz — оптимум для Whisper
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize
)
// чанки по 100мс → WebSocket → partial transcripts
Частота дискретизации 16 кГц достаточна для речи и вдвое меньше данных, чем 44.1 кГц. На iOS аналог — AVAudioEngine с installTap(onBus:).
Важно: WebSocket нужно переоткрывать при потере сети. OkHttp WebSocket на Android имеет коллбек onFailure — реализуйте exponential backoff с максимум 3 попытками, иначе пользователь не поймёт, что соединение оборвалось.
Передача аудио-файла в мультимодальную модель
// iOS — отправка аудио в GPT-4o Audio
let audioData = try Data(contentsOf: recordingURL)
let b64 = audioData.base64EncodedString()
let payload: [String: Any] = [
"model": "gpt-4o-audio-preview",
"messages": [[
"role": "user",
"content": [
["type": "text", "text": userText],
["type": "input_audio", "input_audio": [
"data": b64,
"format": "mp3"
]]
]
]]
]
Лимит на размер аудио у OpenAI — 25 МБ. 30-минутная запись в MP3 128kbps занимает ~28 МБ — не влезает. Для длинного контента нужна нарезка на чанки по 10–15 минут или предварительный Whisper для транскрипции.
Этапы и сроки
Аудит требований (стриминг vs файл, провайдер, целевые платформы) → выбор архитектуры → реализация захвата и конвертации → интеграция STT/мультимодального API → стриминговый UI → тестирование на реальных устройствах (разные микрофоны, фоновый шум, наушники) → публикация.
MVP с записью и Whisper — 1–2 недели. Полная реализация со стримингом, нативным аудио-вводом и обработкой длинных записей — 3–5 недель.







